diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 09c8f0800d..05ece5ddb2 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -44,5 +44,39 @@ *Isaac Seymour* +* Add an `:only` option to `perform_enqueued_jobs` to filter jobs based on + type. + + This allows specific jobs to be tested, while preventing others from + being performed unnecessarily. + + Example: + + def test_hello_job + assert_performed_jobs 1, only: HelloJob do + HelloJob.perform_later('jeremy') + LoggingJob.perform_later + end + end + + An array may also be specified, to support testing multiple jobs. + + Example: + + def test_hello_and_logging_jobs + assert_nothing_raised do + assert_performed_jobs 2, only: [HelloJob, LoggingJob] do + HelloJob.perform_later('jeremy') + LoggingJob.perform_later('stewie') + RescueJob.perform_later('david') + end + end + end + + Fixes #18802. + + *Michael Ryan* + + Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activejob/CHANGELOG.md) for previous changes. diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb index ea9df9a063..c9e2bdca27 100644 --- a/activejob/lib/active_job/queue_adapters/test_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb @@ -11,7 +11,7 @@ module ActiveJob # Rails.application.config.active_job.queue_adapter = :test class TestAdapter delegate :name, to: :class - attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs) + attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter) attr_writer(:enqueued_jobs, :performed_jobs) def initialize @@ -30,22 +30,33 @@ module ActiveJob end def enqueue(job) #:nodoc: - if perform_enqueued_jobs - performed_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name} - Base.execute job.serialize - else - enqueued_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name} - end + return if filtered?(job) + + job_data = { job: job.class, args: job.serialize['arguments'], queue: job.queue_name } + enqueue_or_perform(perform_enqueued_jobs, job, job_data) end def enqueue_at(job, timestamp) #:nodoc: - if perform_enqueued_at_jobs - performed_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp} - Base.execute job.serialize - else - enqueued_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp} - end + return if filtered?(job) + + job_data = { job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp } + enqueue_or_perform(perform_enqueued_at_jobs, job, job_data) end + + private + + def enqueue_or_perform(perform, job, job_data) + if perform + performed_jobs << job_data + Base.execute job.serialize + else + enqueued_jobs << job_data + end + end + + def filtered?(job) + filter && !Array(filter).include?(job.class) + end end end end diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index c544e8a10f..25bc99a4f8 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -125,10 +125,32 @@ module ActiveJob # HelloJob.perform_later('sean') # end # end - def assert_performed_jobs(number) + # + # The block form supports filtering. If the :only option is specified, + # then only the listed job(s) will be performed. + # + # def test_hello_job + # assert_performed_jobs 1, only: HelloJob do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later + # end + # end + # + # An array may also be specified, to support testing multiple jobs. + # + # def test_hello_and_logging_jobs + # assert_nothing_raised do + # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later('stewie') + # RescueJob.perform_later('david') + # end + # end + # end + def assert_performed_jobs(number, only: nil) if block_given? original_count = performed_jobs.size - perform_enqueued_jobs { yield } + perform_enqueued_jobs(only: only) { yield } new_count = performed_jobs.size assert_equal original_count + number, new_count, "#{number} jobs expected, but #{new_count - original_count} were performed" @@ -157,11 +179,33 @@ module ActiveJob # end # end # + # The block form supports filtering. If the :only option is specified, + # then only the listed job(s) will be performed. + # + # def test_hello_job + # assert_performed_jobs 1, only: HelloJob do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later + # end + # end + # + # An array may also be specified, to support testing multiple jobs. + # + # def test_hello_and_logging_jobs + # assert_nothing_raised do + # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later('stewie') + # RescueJob.perform_later('david') + # end + # end + # end + # # Note: This assertion is simply a shortcut for: # # assert_performed_jobs 0, &block - def assert_no_performed_jobs(&block) - assert_performed_jobs 0, &block + def assert_no_performed_jobs(only: nil, &block) + assert_performed_jobs 0, only: only, &block end # Asserts that the job passed in the block has been enqueued with the given arguments. @@ -206,11 +250,12 @@ module ActiveJob queue_adapter.performed_jobs = original_performed_jobs + performed_jobs end - def perform_enqueued_jobs + def perform_enqueued_jobs(only: nil) @old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs @old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs queue_adapter.perform_enqueued_jobs = true queue_adapter.perform_enqueued_at_jobs = true + queue_adapter.filter = only yield ensure queue_adapter.perform_enqueued_jobs = @old_perform_enqueued_jobs diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb index 0a23ae33c4..58de2f2588 100644 --- a/activejob/test/cases/test_helper_test.rb +++ b/activejob/test/cases/test_helper_test.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/date' require 'jobs/hello_job' require 'jobs/logging_job' require 'jobs/nested_job' +require 'jobs/rescue_job' require 'models/person' class EnqueuedJobsTest < ActiveJob::TestCase @@ -283,6 +284,83 @@ class PerformedJobsTest < ActiveJob::TestCase assert_match(/0 .* but 1/, error.message) end + def test_assert_performed_jobs_with_only_option + assert_nothing_raised do + assert_performed_jobs 1, only: HelloJob do + HelloJob.perform_later('jeremy') + LoggingJob.perform_later + end + end + end + + def test_assert_performed_jobs_with_only_option_as_array + assert_nothing_raised do + assert_performed_jobs 2, only: [HelloJob, LoggingJob] do + HelloJob.perform_later('jeremy') + LoggingJob.perform_later('stewie') + RescueJob.perform_later('david') + end + end + end + + def test_assert_performed_jobs_with_only_option_and_none_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 1, only: HelloJob do + LoggingJob.perform_later + end + end + + assert_match(/1 .* but 0/, error.message) + end + + def test_assert_performed_jobs_with_only_option_and_too_few_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 5, only: HelloJob do + HelloJob.perform_later('jeremy') + 4.times { LoggingJob.perform_later } + end + end + + assert_match(/5 .* but 1/, error.message) + end + + def test_assert_performed_jobs_with_only_option_and_too_many_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 1, only: HelloJob do + 2.times { HelloJob.perform_later('jeremy') } + end + end + + assert_match(/1 .* but 2/, error.message) + end + + def test_assert_no_performed_jobs_with_only_option + assert_nothing_raised do + assert_no_performed_jobs only: HelloJob do + LoggingJob.perform_later + end + end + end + + def test_assert_no_performed_jobs_with_only_option_as_array + assert_nothing_raised do + assert_no_performed_jobs only: [HelloJob, RescueJob] do + LoggingJob.perform_later + end + end + end + + def test_assert_no_performed_jobs_with_only_option_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_performed_jobs only: HelloJob do + HelloJob.perform_later('jeremy') + LoggingJob.perform_later + end + end + + assert_match(/0 .* but 1/, error.message) + end + def test_assert_performed_job assert_performed_with(job: NestedJob, queue: 'default') do NestedJob.perform_later