diff options
-rw-r--r-- | activejob/lib/active_job.rb | 2 | ||||
-rw-r--r-- | activejob/lib/active_job/base.rb | 3 | ||||
-rw-r--r-- | activejob/lib/active_job/queue_adapter.rb | 33 | ||||
-rw-r--r-- | activejob/lib/active_job/queue_adapters.rb | 1 | ||||
-rw-r--r-- | activejob/lib/active_job/queue_adapters/test_adapter.rb | 77 | ||||
-rw-r--r-- | activejob/lib/active_job/test_case.rb | 103 | ||||
-rw-r--r-- | activejob/lib/active_job/test_helper.rb | 170 | ||||
-rw-r--r-- | activejob/test/cases/test_helper_test.rb | 217 | ||||
-rw-r--r-- | activejob/test/cases/test_test.rb | 43 | ||||
-rw-r--r-- | railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb | 4 |
10 files changed, 637 insertions, 16 deletions
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb index 29123170b8..93d9d1b2d8 100644 --- a/activejob/lib/active_job.rb +++ b/activejob/lib/active_job.rb @@ -31,4 +31,6 @@ module ActiveJob autoload :Base autoload :QueueAdapters + autoload :TestCase + autoload :TestHelper end diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb index d5ba253016..1b54786303 100644 --- a/activejob/lib/active_job/base.rb +++ b/activejob/lib/active_job/base.rb @@ -8,8 +8,7 @@ require 'active_job/logging' module ActiveJob class Base - extend QueueAdapter - + include QueueAdapter include QueueName include Enqueuing include Execution diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb index 13c23abce4..c597093458 100644 --- a/activejob/lib/active_job/queue_adapter.rb +++ b/activejob/lib/active_job/queue_adapter.rb @@ -3,21 +3,28 @@ require 'active_support/core_ext/string/inflections' module ActiveJob module QueueAdapter - mattr_reader(:queue_adapter) { ActiveJob::QueueAdapters::InlineAdapter } + extend ActiveSupport::Concern - def queue_adapter=(name_or_adapter) - @@queue_adapter = \ - case name_or_adapter - when Symbol, String - load_adapter(name_or_adapter) - when Class - name_or_adapter - end - end + module ClassMethods + delegate :performed_jobs, :performed_jobs=, + :enqueued_jobs, :enqueued_jobs=, + to: ActiveJob::QueueAdapters::TestAdapter + mattr_reader(:queue_adapter) { ActiveJob::QueueAdapters::InlineAdapter } - private - def load_adapter(name) - "ActiveJob::QueueAdapters::#{name.to_s.camelize}Adapter".constantize + def queue_adapter=(name_or_adapter) + @@queue_adapter = \ + case name_or_adapter + when Symbol, String + load_adapter(name_or_adapter) + when Class + name_or_adapter + end end + + private + def load_adapter(name) + "ActiveJob::QueueAdapters::#{name.to_s.camelize}Adapter".constantize + end + end end end
\ No newline at end of file diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb index 007068ff0a..345b01ef00 100644 --- a/activejob/lib/active_job/queue_adapters.rb +++ b/activejob/lib/active_job/queue_adapters.rb @@ -12,5 +12,6 @@ module ActiveJob autoload :SidekiqAdapter autoload :SneakersAdapter autoload :SuckerPunchAdapter + autoload :TestAdapter end end diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb new file mode 100644 index 0000000000..f7c4d19638 --- /dev/null +++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb @@ -0,0 +1,77 @@ +module ActiveJob + module QueueAdapters + class TestAdapter + mattr_accessor(:perform_enqueued_jobs) { false } + mattr_accessor(:perform_enqueued_at_jobs) { false } + + class << self + # Provides a store of all the enqueued jobs with the TestAdapter so you can check them. + def enqueued_jobs + @@enqueued_jobs ||= [] + end + + # Allows you to over write the default enqueued jobs store from an array to some + # other object. If you just want to clear the store, + # call ActiveJob::QueueAdapters::TestAdapter.enqueued_jobs.clear. + # + # If you place another object here, please make sure it responds to: + # + # * << (message) + # * clear + # * length + # * size + # * and other common Array methods + def enqueued_jobs=(val) + @@enqueued_jobs = val + end + + # Provides a store of all the performed jobs with the TestAdapter so you can check them. + def performed_jobs + @@performed_jobs ||= [] + end + + # Allows you to over write the default performed jobs store from an array to some + # other object. If you just want to clear the store, + # call ActiveJob::QueueAdapters::TestAdapter.performed_jobs.clear. + # + # If you place another object here, please make sure it responds to: + # + # * << (message) + # * clear + # * length + # * size + # * and other common Array methods + def performed_jobs=(val) + @@performed_jobs = val + end + + def enqueue(job, *args) + if perform_enqueued_jobs? + performed_jobs << {job: job, args: args, queue: job.queue_name} + job.new.execute(*args) + else + enqueued_jobs << {job: job, args: args, queue: job.queue_name} + end + end + + def enqueue_at(job, timestamp, *args) + if perform_enqueued_at_jobs? + performed_jobs << {job: job, args: args, queue: job.queue_name, run_at: timestamp} + job.new.execute(*args) + else + enqueued_jobs << {job: job, args: args, queue: job.queue_name, run_at: timestamp} + end + end + + private + def perform_enqueued_jobs? + perform_enqueued_jobs + end + + def perform_enqueued_at_jobs? + perform_enqueued_at_jobs + end + end + end + end +end diff --git a/activejob/lib/active_job/test_case.rb b/activejob/lib/active_job/test_case.rb new file mode 100644 index 0000000000..0b76fdb5a7 --- /dev/null +++ b/activejob/lib/active_job/test_case.rb @@ -0,0 +1,103 @@ +# encoding: utf-8 +require 'active_support/test_case' + +module ActiveJob + class NonInferrableJobError < ::StandardError + def initialize(name) + super "Unable to determine the job to test from #{name}. " \ + "You'll need to specify it using 'tests YourJob' in your " \ + 'test case definition' + end + end + + class TestCase < ActiveSupport::TestCase + module Behavior + extend ActiveSupport::Concern + + include ActiveSupport::Testing::ConstantLookup + include ActiveJob::TestHelper + + included do + class_attribute :_job_class + setup :initialize_test_adapter + teardown :restore_previous_adapter + end + + module ClassMethods + def tests(job) + case job + when String, Symbol + self._job_class = job.to_s.camelize.constantize + when Module + self._job_class = job + else + fail NonInferrableJobError.new(job) + end + end + + def job_class + if job = _job_class + job + else + tests determine_default_job(name) + end + end + + def determine_default_job(name) + job = determine_constant_from_test_name(name) do |constant| + Class === constant && constant < ActiveJob::Base + end + fail NonInferrableJobError.new(name) if job.nil? + job + end + end + + protected + def initialize_test_adapter + @old_adapter = ActiveJob::Base.queue_adapter + ActiveJob::Base.queue_adapter = :test + save_test_adapter_behavior + end + + def save_test_adapter_behavior + @old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs + @old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs + end + + def restore_test_adapter_behavior + queue_adapter.perform_enqueued_jobs = @old_perform_enqueued_jobs + queue_adapter.perform_enqueued_at_jobs = @old_perform_enqueued_at_jobs + end + + def restore_previous_adapter + restore_test_adapter_behavior + ActiveJob::Base.queue_adapter = @old_adapter + ActiveJob::Base.performed_jobs.clear + ActiveJob::Base.enqueued_jobs.clear + end + + def perform_enqueued_jobs + queue_adapter.perform_enqueued_jobs = true + end + + def perform_enqueued_at_jobs + queue_adapter.perform_enqueued_at_jobs = true + end + + def enqueue_jobs + queue_adapter.perform_enqueued_jobs = false + end + + def enqueue_at_jobs + queue_adapter.perform_enqueued_at_jobs = false + end + + private + def queue_adapter + ActiveJob::Base.queue_adapter + end + end + + include Behavior + end +end diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb new file mode 100644 index 0000000000..d147e56709 --- /dev/null +++ b/activejob/lib/active_job/test_helper.rb @@ -0,0 +1,170 @@ +# encoding: utf-8 +module ActiveJob + # Provides helper methods for testing Active Job + module TestHelper + # Asserts that the number of enqueued jobs matches the given number. + # + # def test_jobs + # assert_enqueued_jobs 0 + # HelloJob.enqueue('david') + # assert_enqueued_jobs 1 + # HelloJob.enqueue('abdelkader') + # assert_enqueued_jobs 2 + # end + # + # If a block is passed, that block should cause the specified number of + # jobs to be enqueued. + # + # def test_jobs_again + # assert_enqueued_jobs 1 do + # HelloJob.enqueue('cristian') + # end + # + # assert_enqueued_jobs 2 do + # HelloJob.enqueue('aaron') + # HelloJob.enqueue('rafael') + # end + # end + def assert_enqueued_jobs(number) + if block_given? + original_count = enqueued_jobs.size + yield + new_count = enqueued_jobs.size + assert_equal original_count + number, new_count, + "#{number} job expected, but #{new_count - original_count} were enqueued" + else + assert_equal number, enqueued_jobs.size + end + end + + # Assert that no job have been enqueued. + # + # def test_jobs + # assert_no_enqueued_jobs + # HelloJob.enqueue('jeremy') + # assert_enqueued_jobs 1 + # end + # + # If a block is passed, that block should not cause any job to be enqueued. + # + # def test_jobs_again + # assert_no_enqueued_jobs do + # # No job should be enqueued from this block + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_enqueued_jobs 0 + def assert_no_enqueued_jobs(&block) + assert_enqueued_jobs 0, &block + end + + # Asserts that the number of performed jobs matches the given number. + # + # def test_jobs + # assert_performed_jobs 0 + # HelloJob.enqueue('xavier') + # assert_performed_jobs 1 + # HelloJob.enqueue('yves') + # assert_performed_jobs 2 + # end + # + # If a block is passed, that block should cause the specified number of + # jobs to be performed. + # + # def test_jobs_again + # assert_performed_jobs 1 do + # HelloJob.enqueue('robin') + # end + # + # assert_performed_jobs 2 do + # HelloJob.enqueue('carlos') + # HelloJob.enqueue('sean') + # end + # end + def assert_performed_jobs(number) + if block_given? + original_count = self.class.job_class.performed_jobs.size + yield + new_count = performed_jobs.size + assert_equal original_count + number, new_count, + "#{number} job expected, but #{new_count - original_count} were performed" + else + assert_equal number, performed_jobs.size + end + end + + # Assert that no job have been performed. + # + # def test_jobs + # assert_no_performed_jobs + # HelloJob.enqueue('matthew') + # assert_performed_jobs 1 + # end + # + # If a block is passed, that block should not cause any job to be performed. + # + # def test_jobs_again + # assert_no_performed_jobs do + # # No job should be performed from this block + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_performed_jobs 0 + def assert_no_performed_jobs(&block) + assert_performed_jobs 0, &block + end + + # Assert that a job was enqueued in the block matches the args + # + # def assert_enqueued_job + # assert_enqueued_job(job: MyJob, args: [1,2,3], queue: 'low') do + # MyJob.enqueue(1,2,3) + # end + # end + def assert_enqueued_job(args = {}, &_block) + original_enqueued_jobs = enqueued_jobs + enqueued_jobs.clear + args.assert_valid_keys(:job, :args, :at, :queue) + yield + matching_job = enqueued_jobs.any? do |job| + args.all? { |key, value| value == job[key] } + end + assert matching_job + ensure + ActiveJob::Base.enqueued_jobs = original_enqueued_jobs + enqueued_jobs + end + + # Assert that a job was performed in the block matches the args + # + # def assert_performed_job + # assert_performed_job(job: MyJob, args: [1,2,3], queue: 'high') do + # MyJob.enqueue(1,2,3) + # end + # end + def assert_performed_job(args = {}, &_block) + original_performed_jobs = performed_jobs + performed_jobs.clear + args.assert_valid_keys(:job, :args, :at, :queue) + yield + matching_job = performed_jobs.any? do |job| + args.all? { |key, value| value == job[key] } + end + assert matching_job, "No performed job found with #{args}" + ensure + ActiveJob::Base.performed_jobs = original_performed_jobs + performed_jobs + end + + private + def enqueued_jobs + ActiveJob::Base.enqueued_jobs + end + + def performed_jobs + ActiveJob::Base.performed_jobs + end + end +end diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb new file mode 100644 index 0000000000..b98c8e01a5 --- /dev/null +++ b/activejob/test/cases/test_helper_test.rb @@ -0,0 +1,217 @@ +# encoding: utf-8 +require 'helper' +require 'active_support/core_ext/time' +require 'active_support/core_ext/date' +require 'jobs/hello_job' +require 'jobs/logging_job' +require 'jobs/nested_job' + +class EnqueuedJobsTest < ActiveJob::TestCase + tests HelloJob + setup :perform_enqueued_at_jobs + + def test_assert_enqueued_jobs + assert_nothing_raised do + assert_enqueued_jobs 1 do + HelloJob.enqueue('david') + end + end + end + + def test_repeated_enqueued_jobs_calls + assert_nothing_raised do + assert_enqueued_jobs 1 do + HelloJob.enqueue('abdelkader') + end + end + + assert_nothing_raised do + assert_enqueued_jobs 2 do + HelloJob.enqueue('sean') + HelloJob.enqueue('yves') + end + end + end + + def test_assert_enqueued_jobs_with_no_block + assert_nothing_raised do + HelloJob.enqueue('rafael') + assert_enqueued_jobs 1 + end + + assert_nothing_raised do + HelloJob.enqueue('aaron') + HelloJob.enqueue('matthew') + assert_enqueued_jobs 3 + end + end + + def test_assert_no_enqueued_jobs + assert_nothing_raised do + assert_no_enqueued_jobs do + # Scheduled jobs are being performed in this context + HelloJob.enqueue_at(Date.tomorrow.noon, 'godfrey') + end + end + end + + def test_assert_enqueued_jobs_too_few_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_jobs 2 do + HelloJob.enqueue('xavier') + end + end + + assert_match(/2 .* but 1/, error.message) + end + + def test_assert_enqueued_jobs_too_many_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_jobs 1 do + HelloJob.enqueue('cristian') + HelloJob.enqueue('guillermo') + end + end + + assert_match(/1 .* but 2/, error.message) + end + # + def test_assert_no_enqueued_jobs_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_enqueued_jobs do + HelloJob.enqueue('jeremy') + end + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_enqueued_job + assert_enqueued_job(job: LoggingJob, queue: 'default') do + NestedJob.enqueue_at(Date.tomorrow.noon) + end + end + + def test_assert_enqueued_job_failure + assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_job(job: LoggingJob, queue: 'default') do + NestedJob.enqueue + end + end + + assert_raise ActiveSupport::TestCase::Assertion do + assert_enqueued_job(job: NestedJob, queue: 'low') do + NestedJob.enqueue + end + end + end + + def test_assert_enqueued_job_args + assert_raise ArgumentError do + assert_enqueued_job(class: LoggingJob) do + NestedJob.enqueue_at(Date.tomorrow.noon) + end + end + end +end + +class PerformedJobsTest < ActiveJob::TestCase + tests HelloJob + setup :perform_enqueued_jobs + + def test_assert_performed_jobs + assert_nothing_raised do + assert_performed_jobs 1 do + HelloJob.enqueue('david') + end + end + end + + def test_repeated_performed_jobs_calls + assert_nothing_raised do + assert_performed_jobs 1 do + HelloJob.enqueue('abdelkader') + end + end + + assert_nothing_raised do + assert_performed_jobs 2 do + HelloJob.enqueue('sean') + HelloJob.enqueue('yves') + end + end + end + + def test_assert_performed_jobs_with_no_block + assert_nothing_raised do + HelloJob.enqueue('rafael') + assert_performed_jobs 1 + end + + assert_nothing_raised do + HelloJob.enqueue('aaron') + HelloJob.enqueue('matthew') + assert_performed_jobs 3 + end + end + + def test_assert_no_performed_jobs + assert_nothing_raised do + assert_no_performed_jobs do + # Scheduled jobs are being enqueued in this context + HelloJob.enqueue_at(Date.tomorrow.noon, 'godfrey') + end + end + end + + def test_assert_performed_jobs_too_few_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 2 do + HelloJob.enqueue('xavier') + end + end + + assert_match(/2 .* but 1/, error.message) + end + + def test_assert_performed_jobs_too_many_sent + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_jobs 1 do + HelloJob.enqueue('cristian') + HelloJob.enqueue('guillermo') + end + end + + assert_match(/1 .* but 2/, error.message) + end + # + def test_assert_no_performed_jobs_failure + error = assert_raise ActiveSupport::TestCase::Assertion do + assert_no_performed_jobs do + HelloJob.enqueue('jeremy') + end + end + + assert_match(/0 .* but 1/, error.message) + end + + def test_assert_performed_job + assert_performed_job(job: NestedJob, queue: 'default') do + NestedJob.enqueue + end + end + + def test_assert_performed_job_failure + assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_job(job: LoggingJob, queue: 'default') do + NestedJob.enqueue_at(Date.tomorrow.noon) + end + end + + assert_raise ActiveSupport::TestCase::Assertion do + assert_performed_job(job: NestedJob, queue: 'low') do + NestedJob.enqueue_at(Date.tomorrow.noon) + end + end + end +end diff --git a/activejob/test/cases/test_test.rb b/activejob/test/cases/test_test.rb new file mode 100644 index 0000000000..a4d786f3da --- /dev/null +++ b/activejob/test/cases/test_test.rb @@ -0,0 +1,43 @@ +# encoding: utf-8 +require 'helper' +require 'jobs/hello_job' +require 'jobs/logging_job' +require 'jobs/nested_job' + +class ActiveJobTestCaseTest < ActiveJob::TestCase + tests HelloJob + + def test_set_job_class_manual + assert_equal HelloJob, self.class.job_class + end +end + +class CrazySymbolNameJobTest < ActiveJob::TestCase + tests :hello_job + + def test_set_job_class_manual_using_symbol + assert_equal HelloJob, self.class.job_class + end +end + +class CrazyStringNameJobTest < ActiveJob::TestCase + tests 'hello_job' + + def test_set_job_class_manual_using_string + assert_equal HelloJob, self.class.job_class + end +end + +class HelloJobTest < ActiveJob::TestCase + def test_set_job_class_manual + assert_equal HelloJob, self.class.job_class + end +end + +class CrazyNameJobTest < ActiveJob::TestCase + def test_determine_default_job_raises_correct_error + assert_raise(ActiveJob::NonInferrableJobError) do + self.class.job_class + end + end +end diff --git a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb index 6200218313..5a9f0aded9 100644 --- a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb +++ b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb @@ -1,7 +1,9 @@ require 'test_helper' <% module_namespacing do -%> -class <%= class_name %>JobTest < ActiveSupport::TestCase +class <%= class_name %>JobTest < ActiveJob::TestCase + tests <%= class_name %>Job + # test "the truth" do # assert true # end |