Как проверить повторы и сбои при повторной попытке повтора и 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
Ответы
Ответ 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
Там идет честная битка, поэтому я не могу реализовать локально, но, надеюсь, это может помочь вам двигаться в правильном направлении.