Skip to content

Instantly share code, notes, and snippets.

@ChuckJHardy
Last active May 10, 2024 20:10
Show Gist options
  • Save ChuckJHardy/10f54fc567ba3bd4d6f1 to your computer and use it in GitHub Desktop.
Save ChuckJHardy/10f54fc567ba3bd4d6f1 to your computer and use it in GitHub Desktop.
Example ActiveJob with RSpec Tests
class MyJob < ActiveJob::Base
queue_as :urgent
rescue_from(NoResultsError) do
retry_job wait: 5.minutes, queue: :default
end
def perform(*args)
MyService.call(*args)
end
end
require 'rails_helper'
RSpec.describe MyJob, type: :job do
include ActiveJob::TestHelper
subject(:job) { described_class.perform_later(123) }
it 'queues the job' do
expect { job }
.to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :size).by(1)
end
it 'is in urgent queue' do
expect(MyJob.new.queue_name).to eq('urgent')
end
it 'executes perform' do
expect(MyService).to receive(:call).with(123)
perform_enqueued_jobs { job }
end
it 'handles no results error' do
allow(MyService).to receive(:call).and_raise(NoResultsError)
perform_enqueued_jobs do
expect_any_instance_of(MyJob)
.to receive(:retry_job).with(wait: 10.minutes, queue: :default)
job
end
end
after do
clear_enqueued_jobs
clear_performed_jobs
end
end
# As of RSpec 3.4.0 we now have #have_enqueued_job
# https://www.relishapp.com/rspec/rspec-rails/v/3-5/docs/matchers/have-enqueued-job-matcher
RSpec.describe MyJob, type: :job do
subject(:job) { described_class.perform_later(key) }
let(:key) { 123 }
it 'queues the job' do
expect { job }.to have_enqueued_job(described_class)
.with(key)
.on_queue("urgent")
end
end
@ChuckJHardy
Copy link
Author

Updated to include RSpec 3.4.0 #have_enqueued_job

@ollie314
Copy link

ollie314 commented Aug 8, 2017

👍

@jingz
Copy link

jingz commented Sep 6, 2017

👍

@fitchMitch
Copy link

👍

@wiskarindra
Copy link

👍

@kaka-ruto
Copy link

👍

@gogvale
Copy link

gogvale commented Sep 7, 2021

👍

@Proxima89
Copy link

👍

@mandarvaze
Copy link

Is there a way to test that retry happened after :wait period ?

@ChuckJHardy
Copy link
Author

Is there a way to test that retry happened after :wait period ?

Great question @mandarvaze! This feels like testing the internals of Rails, which I avoid to ensure I can move confidently without brittle tests. I am happy to trust that if the expected arguments are passed during the method call, the framework is doing its job as expected. If testing this is critical to your flow, maybe high-level acceptance tests are better suited. Hope this helps.

@mandarvaze
Copy link

@ChuckJHardy Thanks. It does make sense to trust the internals 😄

@chumakoff
Copy link

chumakoff commented Feb 16, 2024

subject(:job) { described_class.perform_later(123) }

it 'queues the job' 

it 'executes perform' 

Those two specs don't test your job. They test the ActiveJob's #perform_later method, wich is pointless.

This kind of test shouldn't use the perform_enqueued_jobs method at all. It's for higher level tests.

@lortza
Copy link

lortza commented Feb 26, 2024

👍

@ToTenMilan
Copy link

ToTenMilan commented May 10, 2024

subject(:job) { described_class.perform_later(123) }

it 'queues the job' 

it 'executes perform' 

Those two specs don't test your job. They test the ActiveJob's #perform_later method, wich is pointless.

This kind of test shouldn't use the perform_enqueued_jobs method at all. It's for higher level tests.

@chumakoff I think these tests are useful. Let's say I have a job like this

  def perform
    User.where(...).find_each do |user|
      UpdateSomething.new(user).call
    end
  end

In the test, I want to stub the internals of UpdateSomething and I want to test whether the query is correct, so whether it calls the service for each user found by the query. executes perform does that

  before { allow_any_instance_of(UpdateSomething).to receive(:call) }

  it 'executes perform' do
    expect_any_instance_of(UpdateSomething).to receive(:call) # if I expect only one call
    perform_enqueued_jobs { job }
  end

Thanks for the gist @ChuckJHardy

@chumakoff
Copy link

chumakoff commented May 10, 2024

@ToTenMilan I agree with you, except the test should have a proper description.

Now the description is it 'executes perform'. It is pointless to check whether :perform_later executes :perform since it is the ActiveJob's code. A proper description would be: it 'calls a service'.

The it 'queues the job' example has both pointless description and implementation. It is not needed to check if :perform_later enqueues the job, for the same reason.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment