Assert zero jobs enqueued using RSpec? - ruby-on-rails

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)

Related

why my job are not performed during test?

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

How to test a Sidekiq worker with RSpec?

I'm doing a test using RSPEC, and used Sidekiq for background jobs.
Since, there's no generator for workers in rspec, not sure what to use.
https://relishapp.com/rspec/rspec-rails/docs/generators
require 'spec_helper'
RSpec.describe TestWorker, type: ? do # ex. :worker :sidekiq ...
describe "TestWorker" do
it "" do
....
end
end
end
bundle exec rspec spec/workers/test_worker_spec.rb
Doing like below, i'm getting: uninitialized constant TestWorker
require 'spec_helper'
describe TestWorker do
it "" do
....
end
end
As i tried, gem rspec-sidekiq
https://github.com/philostler/rspec-sidekiq
Can someone provide a sample template for testing app/workers/ in Rspec.
Thanks.
I have not used the rspec-sidekiq gem, however, here is an example of how I am checking for Background jobs which uses sidekiq
# app/spec/workers/demo_worker_spec.rb
require 'rails_helper'
require 'sidekiq/testing'
Sidekiq::Testing.fake!
RSpec.describe DemoWorker, type: :worker do
describe "Sidekiq Worker" do
let (:demo) { FactoryGirl.create(:demo) }
it "should respond to #perform" do
expect(DemoWorker.new).to respond_to(:perform)
end
describe "Demo" do
before do
Sidekiq::Extensions.enable_delay!
Sidekiq::Worker.clear_all
end
it "should enqueue a Email and SMS job" do
assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
Mailer.delay.demo_request(demo.id)
assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
end
end
end
end
As of I'm checking, if the instance responds to perform.
Then, I'm asserting before and after the job is scheduled.
You might sometimes want to test more than just the fact that the worker has been enqueud or not.
While it is better to decouple complex stuff that could happen in a worker's perform block, it can be tested as a standard class :
it { expect { MyWorker.new.perform }.to change { ...expectations... } }
or
it do
MyWorker.new.perform
... expectations ..
end

How to check what is queued in ActiveJob using Rspec

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

How do you "nest" or "group" Test::Unit tests?

RSpec has:
describe "the user" do
before(:each) do
#user = Factory :user
end
it "should have access" do
#user.should ...
end
end
How would you group tests like that with Test::Unit? For example, in my controller test, I want to test the controller when a user is signed in and when nobody is signed in.
You can achieve something similar through classes. Probably someone will say this is horrible but it does allow you to separate tests within one file:
class MySuperTest < ActiveSupport::TestCase
test "something general" do
assert true
end
class MyMethodTests < ActiveSupport::TestCase
setup do
#variable = something
end
test "my method" do
assert object.my_method
end
end
end
Test::Unit, to my knowledge, does not support test contexts. However, the gem contest adds support for context blocks.
Shoulda https://github.com/thoughtbot/shoulda although it looks like they've now made the context-related code into a separate gem: https://github.com/thoughtbot/shoulda-context
Using shoulda-context:
In your Gemfile:
gem "shoulda-context"
And in your test files you can do things like (notice the should instead of test:
class UsersControllerTest < ActionDispatch::IntegrationTest
context 'Logged out user' do
should "get current user" do
get api_current_user_url
assert_response :success
assert_equal response.body, "{}"
end
end
end

Clearing out ActionMailer::Base.deliveries after RSpec test

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

Resources