Skip to content

Commit

Permalink
Add ActiveJob performed matchers
Browse files Browse the repository at this point in the history
* `have_been_performed` is like `have_been_enqueued`, but for performed
  jobs
* `have_performed_job`/`perform_job` is like
  `have_enqueued_job`/`enqueue_job`, but for performed jobs

Co-authored-by: Phil Pirozhkov hello@fili.pp.ru
  • Loading branch information
isaacseymour authored and pirj committed Dec 13, 2019
1 parent 7d68eb2 commit bcf77ba
Show file tree
Hide file tree
Showing 6 changed files with 552 additions and 6 deletions.
2 changes: 2 additions & 0 deletions Changelog.md
Expand Up @@ -16,6 +16,8 @@ Enhancements:
(David Revelo, #2134)
* Add argument matcher support to `have_enqueued_*` matchers. (Phil Pirozhkov, #2206)
* Switch generated templates to use ruby 1.9 hash keys. (Tanbir Hasan, #2224)
* Add `have_been_performed`/`have_performed_job`/`perform_job` ActiveJob
matchers (Isaac Seymour, #1785)

Bug Fixes:

Expand Down
8 changes: 5 additions & 3 deletions features/job_specs/job_spec.feature
Expand Up @@ -15,9 +15,11 @@ Feature: job spec
* specify the queue which the job was enqueued to

Check the documentation on
[`have_been_enqueued`](matchers/have-been-enqueued-matcher) and
[`have_enqueued_job`](matchers/have-enqueued-job-matcher) for more
information.
[`have_been_enqueued`](matchers/have-been-enqueued-matcher),
[`have_enqueued_job`](matchers/have-enqueued-job-matcher),
[`have_been_performed`](matchers/have-been-performed-matcher), and
[`have_performed_job`](matchers/have-performed-job-matcher)
for more information.

Background:
Given active job is available
Expand Down
77 changes: 77 additions & 0 deletions features/matchers/have_been_performed_matcher.feature
@@ -0,0 +1,77 @@
Feature: have_been_performed matcher

The `have_been_performed` matcher is used to check if given ActiveJob job was performed.

Background:
Given active job is available

Scenario: Checking job class name
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
"""ruby
require "rails_helper"
RSpec.describe UploadBackupsJob do
it "matches with performed job" do
ActiveJob::Base.queue_adapter = :test
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
UploadBackupsJob.perform_later
expect(UploadBackupsJob).to have_been_performed
end
end
"""
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
Then the examples should all pass

Scenario: Checking passed arguments to job
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
"""ruby
require "rails_helper"
RSpec.describe UploadBackupsJob do
it "matches with performed job" do
ActiveJob::Base.queue_adapter = :test
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
UploadBackupsJob.perform_later("users-backup.txt", "products-backup.txt")
expect(UploadBackupsJob).to(
have_been_performed.with("users-backup.txt", "products-backup.txt")
)
end
end
"""
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
Then the examples should all pass

Scenario: Checking job performed time
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
"""ruby
require "rails_helper"
RSpec.describe UploadBackupsJob do
it "matches with performed job" do
ActiveJob::Base.queue_adapter = :test
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
UploadBackupsJob.set(:wait_until => Date.tomorrow.noon).perform_later
expect(UploadBackupsJob).to have_been_performed.at(Date.tomorrow.noon)
end
end
"""
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
Then the examples should all pass

Scenario: Checking job queue name
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
"""ruby
require "rails_helper"
RSpec.describe UploadBackupsJob do
it "matches with performed job" do
ActiveJob::Base.queue_adapter = :test
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
UploadBackupsJob.perform_later
expect(UploadBackupsJob).to have_been_performed.on_queue("default")
end
end
"""
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
Then the examples should all pass
97 changes: 97 additions & 0 deletions features/matchers/have_performed_job_matcher.feature
@@ -0,0 +1,97 @@
Feature: have_performed_job matcher

The `have_performed_job` (also aliased as `perform_job`) matcher is used to check if given ActiveJob job was performed.

Background:
Given active job is available

Scenario: Checking job class name
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
"""ruby
require "rails_helper"
RSpec.describe UploadBackupsJob do
it "matches with performed job" do
ActiveJob::Base.queue_adapter = :test
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
expect {
UploadBackupsJob.perform_later
}.to have_performed_job(UploadBackupsJob)
end
end
"""
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
Then the examples should all pass

Scenario: Checking passed arguments to job
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
"""ruby
require "rails_helper"
RSpec.describe UploadBackupsJob do
it "matches with performed job" do
ActiveJob::Base.queue_adapter = :test
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
expect {
UploadBackupsJob.perform_later("users-backup.txt", "products-backup.txt")
}.to have_performed_job.with("users-backup.txt", "products-backup.txt")
end
end
"""
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
Then the examples should all pass

Scenario: Checking job performed time
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
"""ruby
require "rails_helper"
RSpec.describe UploadBackupsJob do
it "matches with performed job" do
ActiveJob::Base.queue_adapter = :test
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
expect {
UploadBackupsJob.set(:wait_until => Date.tomorrow.noon).perform_later
}.to have_performed_job.at(Date.tomorrow.noon)
end
end
"""
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
Then the examples should all pass

Scenario: Checking job queue name
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
"""ruby
require "rails_helper"
RSpec.describe UploadBackupsJob do
it "matches with performed job" do
ActiveJob::Base.queue_adapter = :test
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
expect {
UploadBackupsJob.perform_later
}.to have_performed_job.on_queue("default")
end
end
"""
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
Then the examples should all pass

Scenario: Using alias method
Given a file named "spec/jobs/upload_backups_job_spec.rb" with:
"""ruby
require "rails_helper"
RSpec.describe UploadBackupsJob do
it "matches with performed job" do
ActiveJob::Base.queue_adapter = :test
ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
expect {
UploadBackupsJob.perform_later
}.to perform_job(UploadBackupsJob)
end
end
"""
When I run `rspec spec/jobs/upload_backups_job_spec.rb`
Then the examples should all pass
117 changes: 114 additions & 3 deletions lib/rspec/rails/matchers/active_job.rb
Expand Up @@ -67,7 +67,7 @@ def thrice
end

def failure_message
"expected to enqueue #{base_message}".tap do |msg|
"expected to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}".tap do |msg|
if @unmatching_jobs.any?
msg << "\nQueued jobs:"
@unmatching_jobs.each do |job|
Expand All @@ -78,7 +78,7 @@ def failure_message
end

def failure_message_when_negated
"expected not to enqueue #{base_message}"
"expected not to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}"
end

def message_expectation_modifier
Expand Down Expand Up @@ -119,7 +119,7 @@ def base_message
msg << " with #{@args}," if @args.any?
msg << " on queue #{@queue}," if @queue
msg << " at #{@at.inspect}," if @at
msg << " but enqueued #{@matching_jobs_count}"
msg << " but #{self.class::MESSAGE_EXPECTATION_ACTION} #{@matching_jobs_count}"
end
end

Expand Down Expand Up @@ -193,6 +193,9 @@ def queue_adapter

# @private
class HaveEnqueuedJob < Base
FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue'.freeze
MESSAGE_EXPECTATION_ACTION = 'enqueued'.freeze

def initialize(job)
super()
@job = job
Expand All @@ -217,6 +220,9 @@ def does_not_match?(proc)

# @private
class HaveBeenEnqueued < Base
FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue'.freeze
MESSAGE_EXPECTATION_ACTION = 'enqueued'.freeze

def matches?(job)
@job = job
check(queue_adapter.enqueued_jobs)
Expand All @@ -228,6 +234,38 @@ def does_not_match?(proc)
!matches?(proc)
end
end

# @private
class HavePerformedJob < Base
FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform'.freeze
MESSAGE_EXPECTATION_ACTION = 'performed'.freeze

def initialize(job)
super()
@job = job
end

def matches?(proc)
raise ArgumentError, "have_performed_job only supports block expectations" unless Proc === proc

original_performed_jobs_count = queue_adapter.performed_jobs.count
proc.call
in_block_jobs = queue_adapter.performed_jobs.drop(original_performed_jobs_count)

check(in_block_jobs)
end
end

# @private
class HaveBeenPerformed < Base
FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform'.freeze
MESSAGE_EXPECTATION_ACTION = 'performed'.freeze

def matches?(job)
@job = job
check(queue_adapter.performed_jobs)
end
end
end

# @api public
Expand Down Expand Up @@ -315,6 +353,79 @@ def have_been_enqueued
ActiveJob::HaveBeenEnqueued.new
end

# @api public
# Passes if a job has been performed inside block. May chain at_least, at_most or exactly to specify a number of times.
#
# @example
# expect {
# perform_jobs { HeavyLiftingJob.perform_later }
# }.to have_performed_job
#
# expect {
# perform_jobs {
# HelloJob.perform_later
# HeavyLiftingJob.perform_later
# }
# }.to have_performed_job(HelloJob).exactly(:once)
#
# expect {
# perform_jobs { 3.times { HelloJob.perform_later } }
# }.to have_performed_job(HelloJob).at_least(2).times
#
# expect {
# perform_jobs { HelloJob.perform_later }
# }.to have_performed_job(HelloJob).at_most(:twice)
#
# expect {
# perform_jobs {
# HelloJob.perform_later
# HeavyLiftingJob.perform_later
# }
# }.to have_performed_job(HelloJob).and have_performed_job(HeavyLiftingJob)
#
# expect {
# perform_jobs {
# HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
# }
# }.to have_performed_job.with(42).on_queue("low").at(Date.tomorrow.noon)
def have_performed_job(job = nil)
check_active_job_adapter
ActiveJob::HavePerformedJob.new(job)
end
alias_method :perform_job, :have_performed_job

# @api public
# Passes if a job has been performed. May chain at_least, at_most or exactly to specify a number of times.
#
# @example
# before do
# ActiveJob::Base.queue_adapter.performed_jobs.clear
# ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
# ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
# end
#
# HeavyLiftingJob.perform_later
# expect(HeavyLiftingJob).to have_been_performed
#
# HelloJob.perform_later
# HeavyLiftingJob.perform_later
# expect(HeavyLiftingJob).to have_been_performed.exactly(:once)
#
# 3.times { HelloJob.perform_later }
# expect(HelloJob).to have_been_performed.at_least(2).times
#
# HelloJob.perform_later
# HeavyLiftingJob.perform_later
# expect(HelloJob).to have_been_performed
# expect(HeavyLiftingJob).to have_been_performed
#
# HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
# expect(HelloJob).to have_been_performed.with(42).on_queue("low").at(Date.tomorrow.noon)
def have_been_performed
check_active_job_adapter
ActiveJob::HaveBeenPerformed.new
end

private

# @private
Expand Down

0 comments on commit bcf77ba

Please sign in to comment.