Подтвердить что ты не робот

Как проверить повторы и сбои при повторной попытке повтора и Rails 4?

Я пытаюсь написать спецификацию, которая проверяет функциональность повтора retque-retry, и я не могу заставить тесты правильно нажать на binding.pry. Есть ли способ проверить эту функциональность с помощью rspec 3, чтобы я мог проверить, что они функционируют по назначению?

Это спецификация запроса, и я пытаюсь имитировать живой запрос через приборы, но независимо от того, что я пытаюсь, я не могу заставить задание повторить попытку.

gem 'resque', require: 'resque/server'
gem 'resque-web', require: 'resque_web'
gem 'resque-scheduler'
gem 'resque-retry'
gem 'resque-lock-timeout'

Я использую resque_rspec и пробую эту стратегию тестирования.

Частичная спецификация

it 'retries it' do
  stub_request(:any, /.*api.bigcartel.*/).to_return(body: '{}', status: 200)
  @order_shipped_json['order']['originator_id'] = @provider_order
  post "/hook/shops/#{@shop.id}", @order_shipped_json.to_json, format: :json
  ResqueSpec.perform_all(queue_name)
  ???
end

Работа в очереди

class QueueHook
  extend Resque::Plugins::LockTimeout
  extend Resque::Plugins::Retry
  extend QueueLock
  extend QueueLogger

  @queue = AppSettings.queues[:hook_queue_name].to_sym
  @lock_timeout = 600
  @retry_exceptions = [QueueError::LockFailed]
  @retry_limit = 600
  @retry_delay = 1

  class << self
    def perform(web_hook_payload_id, _whiplash_customer_id)
      ActiveRecord::Base.clear_active_connections!
      @web_hook_payload = WebHookPayload.find(web_hook_payload_id)
      klass_constructor
      @hook.process_event
    end

    def identifier(_web_hook_payload_id, whiplash_customer_id)
      "lock:integration_hook:#{whiplash_customer_id}"
    end

    def after_perform_delete_webhook(_web_hook_payload_id, _whiplash_customer_id)
      @web_hook_payload.destroy
    end

    private

    ...
  end
end

Модули задания очереди

module QueueLogger
  def before_perform_log_job(*args)
    Rails.logger.info "[Resque][#{self}] running with #{args.inspect}..."
  end

  def on_failure_log_job(*args)
    message = "[Resque][#{self}] failed with #{args.inspect}..."
    run_counters
    Rails.logger.info message_builder(message)
  end

  private

  def run_counters
    @num_attempts += retry_attempt
    @all_attempts += retry_limit
  end

  def message_builder(message)
    return message unless @num_attempts
    return message += " Retrying (attempt ##{@num_attempts + 1})" if @num_attempts < @all_attempts
    message += ' Giving up.'
    message
  end
end

module QueueLock
  def loner_enqueue_failed(*args)
    Rails.logger.info "[Resque][#{self}] is already enqueued: #{args.inspect}..."
  end

  def lock_failed(*)
    raise QueueError::LockFailed
  end
end
4b9b3361

Ответ 1

Таким образом, конкретный сбой, который вы хотите протестировать, исходит из этого используемого вами крюка.

def lock_failed(*)
  raise QueueError::LockFailed
end

Нам нужно вызвать это. Здесь, где он используется в плагине. Поскольку вы используете тайм-аут блокировки, похоже, мы хотим заглушить .acquire_lock_algorithm!. Это опасно, так как этот метод является частью внутреннего api плагина. Имейте это в виду, когда вы обновляете плагин.

it 'retries it' do
  stub_request(:any, /.*api.bigcartel.*/).to_return(body: '{}', status: 200)

  allow(QueueHook).to receive(:acquire_lock_algorithm!).and_return(false, true)

  @order_shipped_json['order']['originator_id'] = @provider_order
  post "/hook/shops/#{@shop.id}", @order_shipped_json.to_json, format: :json

  ResqueSpec.perform_all(queue_name)
end

Эта спецификация должна теперь терпеть неудачу с помощью Failure/Error: raise QueueError::LockFailed. С тех пор, как ожидается, мы можем установить ожидание.

it 'retries it' do
  stub_request(:any, /.*api.bigcartel.*/).to_return(body: '{}', status: 200)

  allow(QueueHook).to receive(:acquire_lock_algorithm!).and_return(false, true)

  @order_shipped_json['order']['originator_id'] = @provider_order
  post "/hook/shops/#{@shop.id}", @order_shipped_json.to_json, format: :json

  expect {
    ResqueSpec.perform_all(queue_name)
  }.to raise_error(QueueError::LockFailed)
end

Теперь спецификация должна проходить, если вы не установили ResqueSpec.inline = true. Если вы установили значение false для этой спецификации. Это будет легче следовать.

Если resque-retry работает, то сбой задания должен был привести к повторной постановке задания в ResqueSpec. Мы можем добавить к этому ожидание. expect(ResqueSpec.queues[queue_name]).to be_present. Нельзя снова запустить задания. Мы высмеивали второе возвращаемое значение acquire_lock_algorithm!, чтобы оно было истинным, поэтому работа должна быть успешной на этот раз.

Так как мы хотим протестировать счетчики, добавим для них читателей

module QueueLogger
  attr_reader :all_attempts, :num_attempts
end

И затем закончить спецификацию...

it 'retries it' do
  stub_request(:any, /.*api.bigcartel.*/).to_return(body: '{}', status: 200)

  allow(QueueHook).to receive(:acquire_lock_algorithm!).and_return(false, true)

  @order_shipped_json['order']['originator_id'] = @provider_order
  post "/hook/shops/#{@shop.id}", @order_shipped_json.to_json, format: :json

  # Failing
  expect {
    ResqueSpec.perform_all(queue_name)
  }.to raise_error(QueueError::LockFailed)
  expect(ResqueSpec.queues[queue_name]).to be_present

  # Retrying
  ResqueSpec.perform_all(queue_name)
  expect(QueueHook.num_attempts).to eq(2)
  ... # Whatever else you want to test.
end

Если вы хотите протестировать ведение журнала, вы их заглушите и установите ожидания относительно того, с кем они звонят. Это должно сделать это, у меня есть упрощенная версия, запущенная на моей собственной машине. Если нет, нам, возможно, придется вдаваться в подробности ваших тестов и конфигураций Resque.

Ответ 2

Несколько заметок -

1) Как уже упоминалось другими, вы, вероятно, хотите отделить обратные вызовы resque от их функциональности. То есть, проверьте, что retries стреляют, но также отдельно проверяют, что они функционируют должным образом. Вы можете разделить их на два отдельных теста.

2) Для проверки того, что они стреляют, я думаю, что вы ищете класс удваивается в RSpec 3.

Вам нужно будет установить double, а затем создать исключение (docs). Это позволит вам увидеть, вызваны ли ваши retries и сколько раз они были вызваны (docs).

Итак, например,

it "retries on exception n number of times" do
  queue_hook = class_double("QueueHook")
  expect(queue_hook).to have_received(:on_failure_log_job).exactly(n).times
  allow(queue_hook).to receive(:perform).and_raise(ExceptionClass, "Exception message")
  queue_hook.perform(payload_id, customer_id)
end

Там идет честная битка, поэтому я не могу реализовать локально, но, надеюсь, это может помочь вам двигаться в правильном направлении.