Welcome @wspurgin as new maintainer for rspec-sidekiq
!
Simple testing of Sidekiq jobs via a collection of matchers and helpers.
Jump to Matchers » | Jump to Helpers »
Installation
group :test do
gem 'rspec-sidekiq'
end
rspec-sidekiq requires sidekiq/testing
by default so there is no need to include the line require "sidekiq/testing"
inside your spec_helper.rb
.
IMPORTANT! This has the effect of not pushing enqueued jobs to Redis but to a job
array to enable testing (see the FAQ & Troubleshooting Wiki page). Thus, only include gem "rspec-sidekiq"
in environments where this behaviour is required, such as the test
group.
Configuration
If you wish to modify the default behaviour, add the following to your spec_helper.rb
file
RSpec::Sidekiq.configure do |config|
config.clear_all_enqueued_jobs = true
config.enable_terminal_colours = true
config.warn_when_jobs_not_processed_by_sidekiq = true
end
Matchers
enqueue_sidekiq_job
Describes that the block should enqueue a job. Optionally specify the
specific job class, arguments, timing, and other context
expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job
expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job(AwesomeJob)
expect { AwesomeJob.perform_async "Awesome!" }.to enqueue_sidekiq_job.with("Awesome!")
expect { AwesomeJob.set(queue: "high").perform_async }.to enqueue_sidekiq_job.on("high")
specific_time = 1.hour.from_now
expect { AwesomeJob.perform_at(specific_time) }.to enqueue_sidekiq_job.at(specific_time)
freeze_time do
expect { AwesomeJob.perform_in(1.hour) }.to enqueue_sidekiq_job.in(1.hour)
end
expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job.once
expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job.exactly(1).time
expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job.exactly(:once)
expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job.at_least(1).time
expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job.at_least(:once)
expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job.at_most(2).times
expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job.at_most(:twice)
expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job.at_most(:thrice)
expect { AwesomeJob.perform_at(specific_time, "Awesome!") }.to(
enqueue_sidekiq_job(AwesomeJob)
.with("Awesome!")
.on("default")
.at(specific_time)
)
expect do
AwesomeJob.perform_async
OtherJob.perform_async
end.to enqueue_sidekiq_job(AwesomeJob).and enqueue_sidekiq_job(OtherJob)
have_enqueued_sidekiq_job
Describes that there should be an enqueued job (with the specified arguments):
AwesomeJob.perform_async 'Awesome', true
expect(AwesomeJob).to have_enqueued_sidekiq_job
expect(AwesomeJob).to have_enqueued_sidekiq_job('Awesome', true)
You can use the built-in RSpec args matchers too:
AwesomeJob.perform_async({"something" => "Awesome", "extra" => "stuff"})
expect(AwesomeJob).to have_enqueued_sidekiq_job(hash_including("something" => "Awesome"))
expect(AwesomeJob).to have_enqueued_sidekiq_job(any_args)
expect(AwesomeJob).to have_enqueued_sidekiq_job(hash_excluding("bad_stuff" => anything))
expect(AwesomeJob).to have_enqueued_sidekiq_job(any_args).and have_enqueued_sidekiq_job(hash_including("something" => "Awesome"))
You can specify the number of jobs enqueued:
expect(AwesomeJob).to have_enqueued_sidekiq_job.once
expect(AwesomeJob).to have_enqueued_sidekiq_job.exactly(1).time
expect(AwesomeJob).to have_enqueued_sidekiq_job.exactly(:once)
expect(AwesomeJob).to have_enqueued_sidekiq_job.at_least(1).time
expect(AwesomeJob).to have_enqueued_sidekiq_job.at_least(:once)
expect(AwesomeJob).to have_enqueued_sidekiq_job.at_most(2).times
expect(AwesomeJob).to have_enqueued_sidekiq_job.at_most(:twice)
expect(AwesomeJob).to have_enqueued_sidekiq_job.at_most(:thrice)
Testing scheduled jobs
Use chainable matchers #at
, #in
and #immediately
time = 5.minutes.from_now
AwesomeJob.perform_at time, 'Awesome', true
expect(AwesomeJob).to have_enqueued_sidekiq_job('Awesome', true).at(time)
AwesomeJob.perform_in 5.minutes, 'Awesome', true
expect(AwesomeJob).to have_enqueued_sidekiq_job('Awesome', true).in(5.minutes)
AwesomeJob.perform_later 5.minutes.ago, 'Awesome', true
expect(AwesomeJob).to have_enqueued_sidekiq_job('Awesome', true).immediately
Testing queue set for job
Use the chainable #on
matcher
class AwesomeJob
include Sidekiq::Job
sidekiq_options queue: :low
end
AwesomeJob.perform_async("a little awesome")
expect(AwesomeJob).to have_enqueued_sidekiq_job("a little awesome").on("low")
AwesomeJob.set(queue: "high").perform_async("Very Awesome!")
expect(AwesomeJob).to have_enqueued_sidekiq_job("Very Awesome!").on("high")
Testing ActiveMailer jobs
user = User.first
AwesomeActionMailer.invite(user, true).deliver_later
expect(Sidekiq::Worker).to have_enqueued_sidekiq_job(
"AwesomeActionMailer",
"invite",
"deliver_now",
user,
true
)
be_processed_in
Describes the queue that a job should be processed in
sidekiq_options queue: :download
expect(AwesomeJob).to be_processed_in :download
it { is_expected.to be_processed_in :download }
be_retryable
Describes if a job should retry when there is a failure in its execution
sidekiq_options retry: 5
expect(AwesomeJob).to be_retryable true
it { is_expected.to be_retryable true }
expect(AwesomeJob).to be_retryable 5
it { is_expected.to be_retryable 5 }
expect(AwesomeJob).to be_retryable false
it { is_expected.to be_retryable false }
save_backtrace
Describes if a job should save the error backtrace when there is a failure in its execution
sidekiq_options backtrace: 5
expect(AwesomeJob).to save_backtrace
it { is_expected.to save_backtrace }
expect(AwesomeJob).to save_backtrace 5
it { is_expected.to save_backtrace 5 }
expect(AwesomeJob).to_not save_backtrace
expect(AwesomeJob).to save_backtrace false
it { is_expected.to_not save_backtrace }
it { is_expected.to save_backtrace false }
be_unique
Describes when a job should be unique within its queue
sidekiq_options unique: true
expect(AwesomeJob).to be_unique
it { is_expected.to be_unique }
be_expired_in
Describes when a job should expire
sidekiq_options expires_in: 1.hour
it { is_expected.to be_expired_in 1.hour }
it { is_expected.to_not be_expired_in 2.hours }
be_delayed
This matcher is deprecated. Use of it with Sidekiq 7+ will raise an error.
Sidekiq 7 dropped Delayed
Extensions.
Describes a method that should be invoked asynchronously (See Sidekiq Delayed Extensions)
Object.delay.is_nil?
expect(Object.method :is_nil?).to be_delayed
Object.delay.is_a? Object
expect(Object.method :is_a?).to be_delayed(Object)
Object.delay_for(1.hour).is_nil?
expect(Object.method :is_nil?).to be_delayed.for 1.hour
Object.delay_for(1.hour).is_a? Object
expect(Object.method :is_a?).to be_delayed(Object).for 1.hour
Object.delay_until(1.hour.from_now).is_nil?
expect(Object.method :is_nil?).to be_delayed.until 1.hour.from_now
Object.delay_until(1.hour.from_now).is_a? Object
expect(Object.method :is_a?).to be_delayed(Object).until 1.hour.from_now
MyMailer.delay.some_mail
expect(MyMailer.instance_method :some_mail).to be_delayed
Example matcher usage
require 'spec_helper'
describe AwesomeJob do
it { is_expected.to be_processed_in :my_queue }
it { is_expected.to be_retryable 5 }
it { is_expected.to be_unique }
it { is_expected.to be_expired_in 1.hour }
it 'enqueues another awesome job' do
subject.perform
expect(AnotherAwesomeJob).to have_enqueued_sidekiq_job('Awesome', true)
end
end
Helpers
Batches
If you are using Sidekiq Batches (Sidekiq Pro feature),
You can opt-in with stub_batches
to make rspec-sidekiq
mock the
implementation (using a NullObject pattern). This enables testing without a
Redis instance. Mocha and RSpec stubbing is supported here.
:warning: Caution: Opting-in to this feature, while allowing you to test without
having Redis, does not provide the exact API that Sidekiq::Batch
does. As
such it can cause surprises.
RSpec.describe "Using mocked batches", stub_batches: true do
it "uses mocked batches" do
batch = Sidekiq::Batch.new
batch.jobs do
SomeJob.perform_async 123
end
expect(SomeJob).to have_enqueued_sidekiq_job
expect { batch.foobar! }.to raise_error(NoMethodError)
end
end
within_sidekiq_retries_exhausted_block
sidekiq_retries_exhausted do |msg|
bar('hello')
end
FooClass.within_sidekiq_retries_exhausted_block {
expect(FooClass).to receive(:bar).with('hello')
}
Testing
bundle exec rspec spec
Maintainers
Alumni
Contribute
Please do! If there's a feature missing that you'd love to see then get in on the action!
Issues/Pull Requests/Comments all welcome...