Как правильно проверить метод retry_on ActiveJob с помощью rspec?
В последние несколько дней я безуспешно пытался протестировать этот метод.
Еще одна вещь, которую я хотел бы сделать - это rescue
ошибка, которая всплывает после последней попытки повторения.
Пожалуйста, смотрите мои комментарии и фрагменты кода ниже.
Исходный код для retry_on здесь также для контекста.
Вот пример кода и тесты:
my_job.rb
retry_on Exception, wait: 2.hours, attempts: 3 do |job, exception|
# some kind of rescue here after job.exceptions == 3
# then notify Bugsnag of failed final attempt.
end
def perform(an_object)
an_object.does_something
end
my_spec.rb
it 'receives retry_on 3 times' do
perform_enqueued_jobs do
expect(AnObject).to receive(:does_something).and_raise { Exception }.exactly(3).times
expect(MyJob).to receive(:retry_on).with(wait: 2.hours, attempts: 3).exactly(3).times
MyJob.perform_later(an_object)
end
assert_performed_jobs 3
end
Ответ теста на неудачу:
1) MyJob.perform receives retry_on 3 times
Failure/Error: expect(job).to receive(:retry_on).with(wait: 4.hours, attempts: 3).exactly(3).times
(MyJob (class)).retry_on({:wait=>2 hours, :attempts=>3})
expected: 3 times with arguments: ({:wait=>2 hours, :attempts=>3})
received: 0 times
# ./spec/jobs/my_job_spec.rb:38:in 'block (4 levels) in <top (required)>'
# ./spec/rails_helper.rb:48:in 'block (3 levels) in <top (required)>'
# ./spec/rails_helper.rb:47:in 'block (2 levels) in <top (required)>'
Я также пытался сделать работу двойной и заглушить метод retry_on
, и это тоже не работает.
Я также попытался использовать Timecop для ускорения пересылки времени ожидания, и тесты по-прежнему не выполняются:
my_spec.rb
it 'receives retry_on 3 times' do
perform_enqueued_jobs do
expect(AnObject).to receive(:does_something).and_raise { Exception }.exactly(3).times
Timecop.freeze(Time.now + 8.hours) do
expect(MyJob).to receive(:retry_on).with(wait: 2.hours, attempts: 3).exactly(3).times
end
MyJob.perform_later(an_object)
end
assert_performed_jobs 3
end
Это метод класса ActiveJob
, и я подтвердил это в терминале byebug
, что это так и есть с моим классом работы.
Разве этот тест не должен работать? Он ожидает, что класс получит метод класса с определенными аргументами. Мой byebug
также получает удар, когда я помещаю его в блок retry_on
, поэтому я знаю, что метод вызывается несколько раз.
Это почти как если бы он был вызван в другом классе, что очень запутанно, и я не думаю, что это так, но я в конце своей веревки с этим.
Я почти решил эту проблему, отделив свои тесты от тестирования самой логики рельсов retry_on
до тестирования моей бизнес-логики вокруг нее. Этот способ также лучше в случае, когда рельсы когда-либо изменяют логику retry_on
.
ОДНАКО, это НЕ работает для более чем одного теста. Если вы используете это более чем в одном случае, последний тест будет прерван и он скажет, что выполнил больше заданий, чем ожидалось.
my_spec.rb
it 'receives retry_on 3 times' do
perform_enqueued_jobs do
allow(AnObject).to receive(:does_something).and_raise { Exception }
expect(AnObject).to receive(:does_something).exactly(3).times
expect(Bugsnag).to receive(:notify).with(Exception).once
MyJob.perform_later(an_object)
end
assert_performed_jobs 3
end
my_job.rb
retry_on Exception, wait: , attempts: 3 do |job, exception|
Bugsnag.notify(exception)
end
def perform(an_object)
an_object.does_something
end
Любая помощь/понимание этого будет принята с благодарностью.
Также хотелось бы получить рекомендацию о том, как обрабатывать всплывающее исключение после максимальных попыток. Я подумываю о том, чтобы вызвать ошибку в блоке retry_on
, а затем вызвать discard_on
триггер для возникшей ошибки.
Спасибо, замечательное сообщество Qaru!
Ответы
Ответ 1
Это формат спецификаций, необходимых для retry_on
который, наконец, работал для меня:
it 'receives retry_on 10 times' do
allow_any_instance_of(MyJob).to receive(:perform).and_raise(MyError.new(nil))
allow_any_instance_of(MyJob).to receive(:executions).and_return(10)
expect(Bugsnag).to receive(:notify)
MyJob.perform_now(an_object)
end
it 'handles error' do
allow_any_instance_of(MyJob).to receive(:perform).and_raise(MyError.new(nil))
expect_any_instance_of(MyJob).to receive(:retry_job)
perform_enqueued_jobs do
MyJob.perform_later(an_object)
end
end
В первом случае executions
- это метод ActiveJob, который запускается, устанавливается и проверяется каждый раз, retry_on
выполняется retry_on
. Мы издеваемся над ним, чтобы вернуть 10, а затем ожидаем, что он позвонит в Bugsnag. retry_on
вызывает только то, что вы дали ему в блоке, как только все attempts
были выполнены. Так что это работает.
Для второго случая, Затем mock ошибка для повышения для экземпляра задания. Затем мы проверяем правильность приема retry_job
(который вызывает retry_on
под капотом), чтобы подтвердить, что он делает правильные вещи. Затем мы perform_later
вызов minitest
блоке minitest
perform_enqueued_jobs
и называем его днем.
Ответ 2
Следующее хорошо работает для меня, также для нескольких тестовых случаев и для тестирования побочных эффектов блока retry_on
.
RSpec.describe MyJob, type: :job do
include ActiveJob::TestHelper
context 'when 'MyError' is raised' do
before do
allow_any_instance_of(described_class).to receive(:perform).and_raise(MyError.new)
end
it 'makes 4 attempts' do
assert_performed_jobs 4 do
described_class.perform_later rescue nil
end
end
it 'does something in the 'retry_on' block' do
expect(Something).to receive(:something)
perform_enqueued_jobs do
described_class.perform_later rescue nil
end
end
end
end
Обратите внимание, что rescue nil
(или какая-либо другая форма спасения) требуется, если вы позволили исключениям всплыть в конце.
Обратите внимание, что perform_now
не считается "заданием в очереди". Таким образом, выполнение described_class.perform_now
приводит к одной меньшей попытке, подсчитанной assert_performed_jobs
.
Ответ 3
В первой спецификации
expect(MyJob).to receive(:retry_on).with(wait: 2.hours, attempts:3).exactly(3).times
Это никогда не будет работать, поскольку метод класса retry_on
будет вызываться на этапе инициализации класса, то есть при загрузке этого класса в память, а не при выполнении спецификации
Во втором spec вы пытались заставить его работать с использованием timecop, но все же не удалось по той же причине
Третья спецификация относительно более реалистична, но
assert_performed_jobs 3
не будет работать без прохождения блока
Что-то вроде
assert_performed_jobs 2 do
//call jobs from here
end
Ответ 4
ИМХО, вы должны оставить тестирование ActiveJob с командой rails.
Вам нужно только убедиться, что вы правильно настраиваете задание:
it 'retries the job 10 times with 2 minutes intervals' do
allow(MyJob).to receive(:retry_on)
load 'app/path/to/job/my_job.rb'
expect(MyJob).to have_received(:retry_on)
.with(
Exception,
wait: 2.minutes,
attempts: 10
)
end