here a small description of my code (simplified)
app/jobs/
class GenerateInvoiceJob < ActiveJob::Base
queue_as :default
def perform()
Invoice.create
end
end
app/models/
class Product < ActiveRecord::Base
def buy
GenerateInvoiceJob.perform_later
end
end
spec/jobs
RSpec.describe AnotherJob, type: :job do
context "with filter" do
...
end
end
spec/models
RSpec.describe Product, type: :model do
describe '#buy' do
it "should generate invoice" do
Product.create().buy
expect(Invoice.all.size).to eq 1
end
end
end
with rails 4.2.11
when I run
rspec spec/models/product_spec.rb
then the test is ok (the job is performed)
when I run
rspec spec -e 'should generate invoice'
then the test fail cause the job is not performed
if I delete all test jobs from spec/jobs and then run
rspec spec -e 'should generate invoice'
then the test is ok (the job is performed)
I can't understand why having some tests for jobs prevents other jobs to perform ? Is there a solution for this?
with rails 5 and rails 6
whatever I do, the test always failed as the job is never performed ?
Aren't jobs performed anymore during tests since rails 5 ?
thanks for help
update 1 after first answer :
thanks a lot for your answer
just to be sure I do correctly :
I added in environment/test.rb
config.active_job.queue_adapter = :test
and in my spec/models/product_spec.rb
RSpec.describe Product, type: :model do
describe '#buy' do
it "should generate invoice" do
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
Product.create().buy
expect(Invoice.all.size).to eq 1
end
end
end
not sure I put
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
at the good place ?!
You need to set:
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
However, using have_enqueued_job is a more common approach.
EDIT: There's even an easier way that slipped my mind:
ActiveJob::Base.queue_adapter = :inline
you need to wrap your code in perform_enqueued_jobs
it 'test request' do
perform_enqueued_jobs do
expect(request).to be_success
end
end
The rspec-rails gem has some support for testing ActiveJob.
Does it provide a way to assert that zero jobs were enqueued?
Rails provides a method called assert_no_enqueued_jobs but it's a little awkward to use in RSpec, because you have to include ::ActiveJob::TestHelper.
RSpec.describe BananaController, type: :controller do
include ::ActiveJob::TestHelper
describe "#create" do
context "access denied" do
it "does not enqueue any jobs" do
# ...
assert_no_enqueued_jobs
end
end
end
end
Is there a better way?
You can try:
expect(ActiveJob::Base.queue_adapter.enqueued_jobs.size).to eq(0)
I'm working on a reset_password method in a Rails API app. When this endpoint is hit, an ActiveJob is queued that will fire off a request to Mandrill (our transactional email client). I'm currently trying to write the tests to ensure that that the ActiveJob is queued correctly when the controller endpoint is hit.
def reset_password
#user = User.find_by(email: params[:user][:email])
#user.send_reset_password_instructions
end
The send_reset_password_instructions creates some url's etc before creating the ActiveJob which's code is below:
class SendEmailJob < ActiveJob::Base
queue_as :default
def perform(message)
mandrill = Mandrill::API.new
mandrill.messages.send_template "reset-password", [], message
rescue Mandrill::Error => e
puts "A mandrill error occurred: #{e.class} - #{e.message}"
raise
end
end
At the moment we are not using any adapters for the ActiveJob, so I just want to check with Rspec that the ActiveJob is queued.
Currently my test looks something like this (I'm using factory girl to create the user):
require 'active_job/test_helper'
describe '#reset_password' do
let(:user) { create :user }
it 'should create an ActiveJob to send the reset password email' do
expect(enqueued_jobs.size).to eq 0
post :reset_password, user: { email: user.email }
expect(enqueued_jobs.size).to eq 1
end
end
Everything works in reality, I just need to create the tests!
I'm using ruby 2.1.2 and rails 4.1.6.
I can't see any documentation or help anywhere on the web on how to test on this so any help would be greatly appreciated!
The accepted answer no longer works for me, so I tried Michael H.'s suggestion in the comments, which works.
describe 'whatever' do
include ActiveJob::TestHelper
after do
clear_enqueued_jobs
end
it 'should email' do
expect(enqueued_jobs.size).to eq(1)
end
end
In a unit test, instead of checking what is queued one can also rely on ActiveJob working properly and just verify that it will be called by mocking its api.
expect(MyJob).to receive(:perform_later).once
post :reset_password, user: { email: user.email }
The creators of the ActiveJob have used the same techniques for their unit tests. See GridJob Testobject
They create a testmock GridJob in their tests and override the perform method, so that it only adds jobs to a custom Array, they call JobBuffer. At the end they test, whether the buffer has jobs enqueued
At a single place one can ofc also do an integrations test. The ActiveJob test_helper.rb is supposed to be used with minitest not with rspec. So you have to rebuild it's functionalitity. You can just call
expect(ActiveJob::Base.queue_adapter.enqueued_jobs).to eq 1
without requiring anything
Update 1:
As noticed within a comment.
ActiveJob::Base.queue_adapter.enqueued_jobs works only by setting it the queue_adapter into test mode.
# either within config/environment/test.rb
config.active_job.queue_adapter = :test
# or within a test setup
ActiveJob::Base.queue_adapter = :test
Rspec 3.4 now has have_enqueued_job cooked in, which makes this a lot easier to test:
it "enqueues a YourJob" do
expect {
get :your_action, {}
}.to have_enqueued_job(YourJob)
end
it has other niceties for have_enqueued_job to allow you to check the argument(s) and the number of times it should be queued up.
Testing Rails ActiveJob with RSpec
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
There is a new rspec extension which makes your life easier.
require 'rails_helper'
RSpec.describe MyController do
let(:user) { FactoryGirl.create(:user) }
let(:params) { { user_id: user.id } }
subject(:make_request) { described_class.make_request(params) }
it { expect { make_request }.to enqueue_a(RequestMaker).with(global_id(user)) }
end
In my opinion, ensure a job was enqueued when a request is performed is important.
You can do it with the below solutions:
Solution 1
expect{ post your_api_here, params: params, headers: headers }
.to have_enqueued_job(YourJob)
.with(args)
Solution 2
expect(YourJob).to receive(:perform_later).once.with(args)
post your_api_here, params: params, headers: headers
I had some problems, maybe because I didn't include ActiveJob::TestHelper, but this worked for me...
Firstly ensure, that you have the queue adapter set to :test as above answers show.
For some reason clear_enqueued_jobs jobs in the after block didn't work for me, but the source shows we can do the following: enqueued_jobs.clear
require 'rails_helper'
include RSpec::Rails::Matchers
RSpec.describe "my_rake_task", type: :rake do
after do
ActiveJob::Base.queue_adapter.enqueued_jobs.clear
end
context "when #all task is run" do
it "enqueues jobs which have been enabled" do
enabled_count = get_enabled_count
subject.execute
expect(ActiveJob::Base.queue_adapter.enqueued_jobs.size).to eq(enabled_count)
end
it "doesn't enqueues jobs which have been disabled" do
enabled_count = get_enabled_count
subject.execute
expect(ActiveJob::Base.queue_adapter.enqueued_jobs.size).to eq(enabled_count)
end
end
end
I think the solutions using expect { your_code }.to have_enqueued_job(YourJob) to be very clean, since they use the "official" assertions. If you do not like long blocks passed to expect, you can also use:
YourJob.perform_later
expect(YourJob).to have_been_enqueued
Please find good examples in the rubydoc documentation.
A simple solution is
# frozen_string_literal: true
class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked
# Most jobs are safe to ignore if the underlying records are no longer available
# discard_on ActiveJob::DeserializationError
#
def self.my_jobs
enqueued_jobs.select{|x| x['job_class'] == self.name}
end
end
then you can use helper method my_jobs in test like
require 'rails_helper'
RSpec.describe SendBookingRemindersJob, type: :job do
describe '.start_time_approaching' do
let!(:booking) { create :booking }
it 'schedules 4 jobs' do
SendBookingRemindersJob.start_time_approaching(booking)
expect(SendBookingRemindersJob.my_jobs.count).to eq(4)
end
end
I have the following RSpec test for my UserMailer class:
require "spec_helper"
describe UserMailer do
it "should send welcome emails" do
ActionMailer::Base.deliveries.should be_empty
user = Factory(:user)
UserMailer.welcome_email(user).deliver
ActionMailer::Base.deliveries.should_not be_empty
end
end
This test passed the first time, but failed the second time I ran it. After doing a little bit of debugging, it appears that the 1st test added an item to the ActionMailer::Base.deliveries array and that item never got cleared out. That causes the first line in the test to fail since the array is not empty.
What's the best way to clear out the ActionMailer::Base.deliveries array after an RSpec test?
RSpec.describe UserMailer do
before do
# ActionMailer::Base.deliveries is a regular array
ActionMailer::Base.deliveries = []
# or use ActionMailer::Base.deliveries.clear
end
it "sends welcome email" do
user = create(:user)
UserMailer.welcome_email(user).deliver_now
expect(ActionMailer::Base.deliveries).to be_present
end
end
You can clear the deliveries after each test quite easily, adding this into your spec_helper.rb.
RSpec.configure do |config|
config.before { ActionMailer::Base.deliveries.clear }
end
I'd suggest reading my article about the correct emails configuration in Rails where I talk also about testing them correctly.
As Andy Lindeman points out, clearing the deliveries is done automatically for mailer tests. However, for other types, simply add , :type => :mailer to the wrapping block to force the same behavior.
describe "tests that send emails", type: :mailer do
# some tests
end
I'm trying out 'shoulda' on top of rspec (rails 3) with the following spec:
require 'spec_helper'
describe Article do
should "be true" do
assert true
end
end
and it fails with
/Users/jeppe/.rvm/gems/ruby-1.8.7-p302/gems/rspec-expectations-2.0.0.beta.20/lib/rspec/expectations/handler.rb:11:in `handle_matcher': undefined method `matches?' for "be true":String (NoMethodError)
Now my tests will run just fine when I do both
require 'spec_helper'
describe Article do
it "should be true" do
assert true
end
end
and
require 'spec_helper'
describe Article do
it { should belong_to :issue }
it { should have_and_belong_to_many :pages }
it { should have_many :tasks }
end
where the last uses Shoulda::ActiveRecord::Matchers, so to my knowledge shoulda is loaded allright.
Any suggestions?
In RSpec should is an RSpec method used to trigger a matcher - it is not Shouldas context block. For that, you use RSpecs own describe.
should "be true" do
assert true
end
is Shoulda's Test::Unit based syntax, which shouldn't work in RSpec examples (I guess?). Just use your second example, which has the same effect and the right syntax.