Testing unique jobs
In your own test suite, disable uniqueness and trust the gem — reach for real Redis only when you're testing lock behavior on purpose.
Disable uniqueness in your tests#
Turn the gem off so it never fights your test setup.
The uniqueness machinery is there to protect production, not to be re-exercised by every app that installs the gem. In your own suite it mostly gets in the way: a job that was locked in one example can silently refuse to enqueue in the next, and your test failures start pointing at Redis state instead of your code.
The fix is one line. Turn uniqueness off globally, once, in your test boot:
# spec/rails_helper.rb (or spec/spec_helper.rb)
require "sidekiq_unique_jobs"
RSpec.configure do |config|
config.before(:suite) do
SidekiqUniqueJobs.config.enabled = false
end
endWith enabled = false, the client and server middleware become no-ops: jobs enqueue and run exactly as they would without the gem, and no locks are written to Redis. Your tests assert on your behavior — that a job was enqueued, that it did the right thing — without any uniqueness in the picture.
When you do need to test locking#
Use real Redis, real testing modes, and clean state between examples.
Occasionally you are testing the lock itself — a custom lock_args method, or that a specific worker is configured for uniqueness at all. When you do, two things matter: use real Redis (the lock lives in Lua scripts, so mocks can't reproduce it), and clear it between examples so one test never inherits another's locks.
Enable uniqueness for just those examples and flush Redis around each one:
RSpec.describe "locking behavior" do
before do
SidekiqUniqueJobs.config.enabled = true
SidekiqUniqueJobs.redis(&:flushdb)
end
after do
SidekiqUniqueJobs.config.enabled = false
end
endTesting modes
Sidekiq's testing modes change when jobs run, and that changes what uniqueness you can observe:
Sidekiq::Testing.inline!runs a job the moment it's enqueued, on the same thread — with the client and server middleware both firing, so uniqueness is fully enforced. Use it to test end-to-end lock behavior.Sidekiq::Testing.disable!pushes jobs to real Redis without running them, so only the client (enqueue-time) lock applies. Use it to test that a duplicate enqueue is prevented while nothing is executing.
A worker that locks from enqueue until execution completes:
class RefreshReportJob
include Sidekiq::Job
sidekiq_options lock: :until_executed
def perform(report_id)
Report.find(report_id).refresh!
end
endTesting that the client lock blocks a duplicate enqueue. Because disable! pushes to real Redis, RefreshReportJob.jobs (the fake in-memory array) stays empty — assert on the real queue instead, and clear it around the example:
RSpec.describe RefreshReportJob do
before do
SidekiqUniqueJobs.config.enabled = true
SidekiqUniqueJobs.redis(&:flushdb)
Sidekiq::Queue.new("default").clear
Sidekiq::Testing.disable!
end
after do
Sidekiq::Queue.new("default").clear
SidekiqUniqueJobs.config.enabled = false
end
it "enqueues only one job per report" do
described_class.perform_async(42)
described_class.perform_async(42)
expect(Sidekiq::Queue.new("default").size).to eq(1)
end
endTesting that the lock is held across a full inline run:
RSpec.describe RefreshReportJob do
before do
SidekiqUniqueJobs.config.enabled = true
SidekiqUniqueJobs.redis(&:flushdb)
Sidekiq::Testing.inline!
end
after { SidekiqUniqueJobs.config.enabled = false }
it "runs the job and releases the lock afterward" do
expect(Report).to receive(:find).with(42).and_call_original
described_class.perform_async(42)
expect(SidekiqUniqueJobs::Digests.new.count).to eq(0)
end
endOverriding config for a single block#
use_config restores everything when the block returns.
When one example needs a different setting — a shorter lock_ttl, a specific digest_algorithm, uniqueness switched on — reach for SidekiqUniqueJobs.use_config. It applies the overrides for the duration of the block and restores the previous values on the way out, even if the block raises, so no other test inherits your change.
RSpec.describe RefreshReportJob do
before do
SidekiqUniqueJobs.redis(&:flushdb)
Sidekiq::Queue.new("default").clear
end
after { Sidekiq::Queue.new("default").clear }
it "enforces uniqueness only inside the block" do
SidekiqUniqueJobs.use_config(enabled: true) do
Sidekiq::Testing.disable!
described_class.perform_async(42)
described_class.perform_async(42)
# disable! pushes to real Redis, so assert on the real queue.
expect(Sidekiq::Queue.new("default").size).to eq(1)
end
# enabled is back to whatever it was before the block.
end
endAny config key works the same way — pass as many as you need:
SidekiqUniqueJobs.use_config(enabled: true, lock_ttl: 5, digest_algorithm: :modern) do
# ... assertions that depend on these settings ...
endThe one rule to remember#
The gem ships with a thorough suite that covers every lock type, every conflict strategy, and the reaper — end to end, against real Redis. You don't need to reproduce any of that in your application. Testing "does :until_executed actually prevent a duplicate?" is testing the gem, not your app.
- In unit tests: keep
SidekiqUniqueJobs.config.enabled = false. - In the rare lock-behavior test: enable it, use real Redis, flush between examples, and pick the testing mode that matches the lock phase you're checking.