@@ -67,7 +67,7 @@ def thrice
6767 end
6868
6969 def failure_message
70- "expected to enqueue #{ base_message } " . tap do |msg |
70+ "expected to #{ self . class :: FAILURE_MESSAGE_EXPECTATION_ACTION } #{ base_message } " . tap do |msg |
7171 if @unmatching_jobs . any?
7272 msg << "\n Queued jobs:"
7373 @unmatching_jobs . each do |job |
@@ -78,7 +78,7 @@ def failure_message
7878 end
7979
8080 def failure_message_when_negated
81- "expected not to enqueue #{ base_message } "
81+ "expected not to #{ self . class :: FAILURE_MESSAGE_EXPECTATION_ACTION } #{ base_message } "
8282 end
8383
8484 def message_expectation_modifier
@@ -119,7 +119,7 @@ def base_message
119119 msg << " with #{ @args } ," if @args . any?
120120 msg << " on queue #{ @queue } ," if @queue
121121 msg << " at #{ @at . inspect } ," if @at
122- msg << " but enqueued #{ @matching_jobs_count } "
122+ msg << " but #{ self . class :: MESSAGE_EXPECTATION_ACTION } #{ @matching_jobs_count } "
123123 end
124124 end
125125
@@ -193,6 +193,9 @@ def queue_adapter
193193
194194 # @private
195195 class HaveEnqueuedJob < Base
196+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue' . freeze
197+ MESSAGE_EXPECTATION_ACTION = 'enqueued' . freeze
198+
196199 def initialize ( job )
197200 super ( )
198201 @job = job
@@ -217,6 +220,9 @@ def does_not_match?(proc)
217220
218221 # @private
219222 class HaveBeenEnqueued < Base
223+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue' . freeze
224+ MESSAGE_EXPECTATION_ACTION = 'enqueued' . freeze
225+
220226 def matches? ( job )
221227 @job = job
222228 check ( queue_adapter . enqueued_jobs )
@@ -228,6 +234,38 @@ def does_not_match?(proc)
228234 !matches? ( proc )
229235 end
230236 end
237+
238+ # @private
239+ class HavePerformedJob < Base
240+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform' . freeze
241+ MESSAGE_EXPECTATION_ACTION = 'performed' . freeze
242+
243+ def initialize ( job )
244+ super ( )
245+ @job = job
246+ end
247+
248+ def matches? ( proc )
249+ raise ArgumentError , "have_performed_job only supports block expectations" unless Proc === proc
250+
251+ original_performed_jobs_count = queue_adapter . performed_jobs . count
252+ proc . call
253+ in_block_jobs = queue_adapter . performed_jobs . drop ( original_performed_jobs_count )
254+
255+ check ( in_block_jobs )
256+ end
257+ end
258+
259+ # @private
260+ class HaveBeenPerformed < Base
261+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform' . freeze
262+ MESSAGE_EXPECTATION_ACTION = 'performed' . freeze
263+
264+ def matches? ( job )
265+ @job = job
266+ check ( queue_adapter . performed_jobs )
267+ end
268+ end
231269 end
232270
233271 # @api public
@@ -315,6 +353,79 @@ def have_been_enqueued
315353 ActiveJob ::HaveBeenEnqueued . new
316354 end
317355
356+ # @api public
357+ # Passes if a job has been performed inside block. May chain at_least, at_most or exactly to specify a number of times.
358+ #
359+ # @example
360+ # expect {
361+ # perform_jobs { HeavyLiftingJob.perform_later }
362+ # }.to have_performed_job
363+ #
364+ # expect {
365+ # perform_jobs {
366+ # HelloJob.perform_later
367+ # HeavyLiftingJob.perform_later
368+ # }
369+ # }.to have_performed_job(HelloJob).exactly(:once)
370+ #
371+ # expect {
372+ # perform_jobs { 3.times { HelloJob.perform_later } }
373+ # }.to have_performed_job(HelloJob).at_least(2).times
374+ #
375+ # expect {
376+ # perform_jobs { HelloJob.perform_later }
377+ # }.to have_performed_job(HelloJob).at_most(:twice)
378+ #
379+ # expect {
380+ # perform_jobs {
381+ # HelloJob.perform_later
382+ # HeavyLiftingJob.perform_later
383+ # }
384+ # }.to have_performed_job(HelloJob).and have_performed_job(HeavyLiftingJob)
385+ #
386+ # expect {
387+ # perform_jobs {
388+ # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
389+ # }
390+ # }.to have_performed_job.with(42).on_queue("low").at(Date.tomorrow.noon)
391+ def have_performed_job ( job = nil )
392+ check_active_job_adapter
393+ ActiveJob ::HavePerformedJob . new ( job )
394+ end
395+ alias_method :perform_job , :have_performed_job
396+
397+ # @api public
398+ # Passes if a job has been performed. May chain at_least, at_most or exactly to specify a number of times.
399+ #
400+ # @example
401+ # before do
402+ # ActiveJob::Base.queue_adapter.performed_jobs.clear
403+ # ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
404+ # ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
405+ # end
406+ #
407+ # HeavyLiftingJob.perform_later
408+ # expect(HeavyLiftingJob).to have_been_performed
409+ #
410+ # HelloJob.perform_later
411+ # HeavyLiftingJob.perform_later
412+ # expect(HeavyLiftingJob).to have_been_performed.exactly(:once)
413+ #
414+ # 3.times { HelloJob.perform_later }
415+ # expect(HelloJob).to have_been_performed.at_least(2).times
416+ #
417+ # HelloJob.perform_later
418+ # HeavyLiftingJob.perform_later
419+ # expect(HelloJob).to have_been_performed
420+ # expect(HeavyLiftingJob).to have_been_performed
421+ #
422+ # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
423+ # expect(HelloJob).to have_been_performed.with(42).on_queue("low").at(Date.tomorrow.noon)
424+ def have_been_performed
425+ check_active_job_adapter
426+ ActiveJob ::HaveBeenPerformed . new
427+ end
428+
318429 private
319430
320431 # @private
0 commit comments