Skip to content

Instantly share code, notes, and snippets.

@ChuckJHardy
Last active June 28, 2024 12:52
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
@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