Sidekiq Testing
tl;dr Make your tests better with Sidekiq’s built-in testing support.
Say I’ve got a Sidekiq worker that takes a list of numbers, and schedules another worker to handle the processing of each number.
class DelegatorWorker
include Sidekiq::Worker
def perform(args)
Array(args["numbers"]).each do |number|
DelegateWorker.perform_async("number" => number)
end
end
end
How do I test this? With RSpec, I’d be tempted to use its amazing test double facilities.
RSpec.describe DelegatorWorker do
describe "#perform" do
it "delegates to DelegateWorker" do
allow(DelegateWorker).to receive(:perform_async)
described_class.new.perform("numbers" => [1, 2])
expect(DelegateWorker).to have_received(:perform_async).with(1)
expect(DelegateWorker).to have_received(:perform_async).with(2)
end
end
end
And that’s fine as far as it goes.
I’m asserting that DelegatorWorker
conforms to Sidekiq’s calling interface
(the perform_async
method).
I’ve mocked something I don’t own,
but it’s not an unreasonable tradeoff for such a simple situation.
But what happens if I want to add some job-related metadata to each delegated job?
Maybe I’ve got some Sidekiq middleware that handles odd numbers differently.
class DelegatorWorker
include Sidekiq::Worker
def perform(args)
Array(args["numbers"]).each do |number|
DelegateWorker.set("odd" => number.odd?)
.perform_async("number" => number)
end
end
end
The tests won’t break,
because DelegateWorker.set
returns the worker class.
But integrating tests for that set
call will get gnarly.
I could get fancier with RSpec,
maybe return some spies when set
is called with specific arguments,
but that kind of test cruft piles up quickly,
and impairs the readability of the test.
Fortunately, Sidekiq itself provides a good answer with its built-in testing support. Let’s rewrite the original test using Sidekiq stuff, without regard to the new odd-number requirement:
RSpec.describe DelegatorWorker do
describe "#perform" do
it "delegates to DelegateWorker" do
described_class.new.perform("numbers" => [1, 2])
expect(DelegateWorker.jobs).to include(
hash_including("args" => [{ "number" => 1 }]),
hash_including("args" => [{ "number" => 2 }])
)
end
end
end
Note the secret sauce, which is the jobs
method that Sidekiq’s testing module adds to Sidekiq workers.
I still get to flex some RSpec-fu too,
using hash_including
to focus on only the pertinent parts of the job.
Far more importantly, the test is quite readable.
There’s no test-double cruft,
just clean execution and assertion.
Asserting the new odd
setting is trivial:
RSpec.describe DelegatorWorker do
describe "#perform" do
it "delegates to DelegateWorker" do
described_class.new.perform("numbers" => [1, 2])
expect(DelegateWorker.jobs).to include(
hash_including("odd" => true, "args" => [{ "number" => 1 }]),
hash_including("odd" => false, "args" => [{ "number" => 2 }])
)
end
end
end
There’s plenty more good stuff in Sidekiq where that came from. Do yourself a favor and leverage it in your tests.
Questions? Comments? Contact me!
Tools Used
- RSpec
- 3.9.0
- Ruby
- 2.6.5p114
- Sidekiq
- 6.0.3