Skip to content

Commit 5c1b113

Browse files
committed
Merge pull request #1785 from isaacseymour/activejob-performed-matchers
ActiveJob performed matchers
1 parent 203ea45 commit 5c1b113

File tree

6 files changed

+553
-6
lines changed

6 files changed

+553
-6
lines changed

Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Enhancements:
1616
(David Revelo, #2134)
1717
* Add argument matcher support to `have_enqueued_*` matchers. (Phil Pirozhkov, #2206)
1818
* Switch generated templates to use ruby 1.9 hash keys. (Tanbir Hasan, #2224)
19+
* Add `have_been_performed`/`have_performed_job`/`perform_job` ActiveJob
20+
matchers (Isaac Seymour, #1785)
1921

2022
Bug Fixes:
2123

features/job_specs/job_spec.feature

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ Feature: job spec
1515
* specify the queue which the job was enqueued to
1616

1717
Check the documentation on
18-
[`have_been_enqueued`](matchers/have-been-enqueued-matcher) and
19-
[`have_enqueued_job`](matchers/have-enqueued-job-matcher) for more
20-
information.
18+
[`have_been_enqueued`](matchers/have-been-enqueued-matcher),
19+
[`have_enqueued_job`](matchers/have-enqueued-job-matcher),
20+
[`have_been_performed`](matchers/have-been-performed-matcher), and
21+
[`have_performed_job`](matchers/have-performed-job-matcher)
22+
for more information.
2123

2224
Background:
2325
Given active job is available
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
Feature: have_been_performed matcher
2+
3+
The `have_been_performed` matcher is used to check if given ActiveJob job was performed.
4+
5+
Background:
6+
Given active job is available
7+
8+
Scenario: Checking job class name
9+
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
10+
"""ruby
11+
require "rails_helper"
12+
13+
RSpec.describe UploadBackupsJob do
14+
it "matches with performed job" do
15+
ActiveJob::Base.queue_adapter = :test
16+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
17+
UploadBackupsJob.perform_later
18+
expect(UploadBackupsJob).to have_been_performed
19+
end
20+
end
21+
"""
22+
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
23+
Then the examples should all pass
24+
25+
Scenario: Checking passed arguments to job
26+
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
27+
"""ruby
28+
require "rails_helper"
29+
30+
RSpec.describe UploadBackupsJob do
31+
it "matches with performed job" do
32+
ActiveJob::Base.queue_adapter = :test
33+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
34+
UploadBackupsJob.perform_later("users-backup.txt", "products-backup.txt")
35+
expect(UploadBackupsJob).to(
36+
have_been_performed.with("users-backup.txt", "products-backup.txt")
37+
)
38+
end
39+
end
40+
"""
41+
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
42+
Then the examples should all pass
43+
44+
Scenario: Checking job performed time
45+
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
46+
"""ruby
47+
require "rails_helper"
48+
49+
RSpec.describe UploadBackupsJob do
50+
it "matches with performed job" do
51+
ActiveJob::Base.queue_adapter = :test
52+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
53+
ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
54+
UploadBackupsJob.set(:wait_until => Date.tomorrow.noon).perform_later
55+
expect(UploadBackupsJob).to have_been_performed.at(Date.tomorrow.noon)
56+
end
57+
end
58+
"""
59+
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
60+
Then the examples should all pass
61+
62+
Scenario: Checking job queue name
63+
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
64+
"""ruby
65+
require "rails_helper"
66+
67+
RSpec.describe UploadBackupsJob do
68+
it "matches with performed job" do
69+
ActiveJob::Base.queue_adapter = :test
70+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
71+
UploadBackupsJob.perform_later
72+
expect(UploadBackupsJob).to have_been_performed.on_queue("default")
73+
end
74+
end
75+
"""
76+
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
77+
Then the examples should all pass
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
Feature: have_performed_job matcher
2+
3+
The `have_performed_job` (also aliased as `perform_job`) matcher is used to check if given ActiveJob job was performed.
4+
5+
Background:
6+
Given active job is available
7+
8+
Scenario: Checking job class name
9+
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
10+
"""ruby
11+
require "rails_helper"
12+
13+
RSpec.describe UploadBackupsJob do
14+
it "matches with performed job" do
15+
ActiveJob::Base.queue_adapter = :test
16+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
17+
expect {
18+
UploadBackupsJob.perform_later
19+
}.to have_performed_job(UploadBackupsJob)
20+
end
21+
end
22+
"""
23+
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
24+
Then the examples should all pass
25+
26+
Scenario: Checking passed arguments to job
27+
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
28+
"""ruby
29+
require "rails_helper"
30+
31+
RSpec.describe UploadBackupsJob do
32+
it "matches with performed job" do
33+
ActiveJob::Base.queue_adapter = :test
34+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
35+
expect {
36+
UploadBackupsJob.perform_later("users-backup.txt", "products-backup.txt")
37+
}.to have_performed_job.with("users-backup.txt", "products-backup.txt")
38+
end
39+
end
40+
"""
41+
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
42+
Then the examples should all pass
43+
44+
Scenario: Checking job performed time
45+
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
46+
"""ruby
47+
require "rails_helper"
48+
49+
RSpec.describe UploadBackupsJob do
50+
it "matches with performed job" do
51+
ActiveJob::Base.queue_adapter = :test
52+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
53+
ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
54+
expect {
55+
UploadBackupsJob.set(:wait_until => Date.tomorrow.noon).perform_later
56+
}.to have_performed_job.at(Date.tomorrow.noon)
57+
end
58+
end
59+
"""
60+
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
61+
Then the examples should all pass
62+
63+
Scenario: Checking job queue name
64+
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
65+
"""ruby
66+
require "rails_helper"
67+
68+
RSpec.describe UploadBackupsJob do
69+
it "matches with performed job" do
70+
ActiveJob::Base.queue_adapter = :test
71+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
72+
expect {
73+
UploadBackupsJob.perform_later
74+
}.to have_performed_job.on_queue("default")
75+
end
76+
end
77+
"""
78+
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
79+
Then the examples should all pass
80+
81+
Scenario: Using alias method
82+
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
83+
"""ruby
84+
require "rails_helper"
85+
86+
RSpec.describe UploadBackupsJob do
87+
it "matches with performed job" do
88+
ActiveJob::Base.queue_adapter = :test
89+
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
90+
expect {
91+
UploadBackupsJob.perform_later
92+
}.to perform_job(UploadBackupsJob)
93+
end
94+
end
95+
"""
96+
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
97+
Then the examples should all pass

lib/rspec/rails/matchers/active_job.rb

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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 << "\nQueued 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

Comments
 (0)