Skip to content

Commit a9d666a

Browse files
committed
Fixup have_enqueued_job matcher on job retries
Previously we were checking only job counts, so if one job was performed and one job was added - matcher failed. Check by unique id (`#hash`) instead. We cannot use `job['job_id']` here, because job retains `job_id` on retry. Also we need to count jobs before & after because multiple `#perform_later` in rails-6.0 create jobs with the same `#hash`, at least in some cases, using `:test` AJ adapter.
1 parent 1873102 commit a9d666a

File tree

2 files changed

+46
-3
lines changed

2 files changed

+46
-3
lines changed

lib/rspec/rails/matchers/active_job.rb

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,26 @@ def initialize(job)
230230
def matches?(proc)
231231
raise ArgumentError, "have_enqueued_job and enqueue_job only support block expectations" unless Proc === proc
232232

233-
original_enqueued_jobs_count = queue_adapter.enqueued_jobs.count
233+
original_enqueued_jobs_hashes = queue_adapter.enqueued_jobs.map(&:hash)
234+
234235
proc.call
235-
in_block_jobs = queue_adapter.enqueued_jobs.drop(original_enqueued_jobs_count)
236236

237-
check(in_block_jobs)
237+
in_block_jobs = queue_adapter.enqueued_jobs.each_with_object({}) do |job, jobs|
238+
jobs[job.hash] ||= {job: job, count: 0}
239+
jobs[job.hash][:count] += 1
240+
end
241+
242+
original_enqueued_jobs_hashes.each do |job_hash|
243+
in_block_jobs[job_hash][:count] -= 1 if in_block_jobs.key?(job_hash)
244+
end
245+
246+
in_block_jobs = in_block_jobs.each_value.flat_map do |job_and_count|
247+
count, job = job_and_count.values_at(:count, :job)
248+
249+
Array.new(count, job) if count.positive?
250+
end
251+
252+
check(in_block_jobs.compact)
238253
end
239254

240255
def does_not_match?(proc)

spec/rspec/rails/matchers/active_job_spec.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,34 @@ def self.name; "LoggingJob"; end
9898
expect { }.not_to have_enqueued_job
9999
end
100100

101+
context "when job is retried" do
102+
include ActiveJob::TestHelper
103+
104+
let(:retried_job) do
105+
Class.new(ActiveJob::Base) do
106+
retry_on StandardError, wait: 5, queue: :retry
107+
108+
def self.name; "RetriedJob"; end
109+
def perform; raise StandardError; end
110+
end
111+
end
112+
113+
before do
114+
stub_const("RetriedJob", retried_job)
115+
queue_adapter.perform_enqueued_jobs = true
116+
end
117+
118+
it "passes with reenqueued job" do
119+
time = Time.current.change(usec: 0)
120+
121+
retried_job.perform_later
122+
123+
travel_to time do
124+
expect { perform_enqueued_jobs }.to have_enqueued_job(retried_job).on_queue(:retry).at(time + 5)
125+
end
126+
end
127+
end
128+
101129
it "fails when job is not enqueued" do
102130
expect {
103131
expect { }.to have_enqueued_job

0 commit comments

Comments
 (0)