How to properly test ActiveJob's retry_on method with rspec?

This is the format of specs needed for retry_on that finally worked for me:

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

For the first case, executions is an ActiveJob method that gets run, set and checked every time retry_on is executed. We mock it to return 10 and then expect it to call Bugsnag. retry_on only calls what you gave it in the block once all the attempts have been met. So this works.

For the second case, Then mock the error to raise for the job instance. Next we check that it's correctly receiving retry_job (which retry_on calls under the hood) to confirm it's doing the right thing. Then we wrap the perform_later call in the minitest perform_enqueued_jobs block and call it a day.


The following works fine for me, also for multiple testcases and for testing side effects of the retry_on block.

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

Note that rescue nil (or some form of rescue) is required if you let exceptions bubble up at the end.

Note that perform_now doesn't count as "enqueued job". So doing described_class.perform_now results in one less attempts counted by assert_performed_jobs.


IMHO you should leave the testing of ActiveJob with the rails team.

You only need to make sure you're configuring the job properly:

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