Skip to content

Commit

Permalink
Active Job: Correctly use the desired test adapter in tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiculescu committed May 7, 2024
1 parent e8e077d commit e846921
Show file tree
Hide file tree
Showing 12 changed files with 466 additions and 18 deletions.
13 changes: 13 additions & 0 deletions activejob/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
* All tests now respect the `active_job.queue_adapter` config

Previously if you had set `config.active_job.queue_adapter` in your `config/application.rb`
or `config/environments/test.rb` file, the adapter you selected was previously not used consistently
across all tests. In some tests your adapter would be used, but other tests would use the `TestAdapter`.

In Rails 7.1, all tests will respect the `queue_adapter` config if provided. If no config is provided,
the `TestAdapter` will continue to be used.

See [#48585](https://github.com/rails/rails/pull/48585) for more details.

*Alex Ghiculescu*

* Make Active Job transaction aware when used conjointly with Active Record.

A common mistake with Active Job is to enqueue jobs from inside a transaction,
Expand Down
20 changes: 14 additions & 6 deletions activejob/lib/active_job/queue_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ def adapter_name(adapter) # :nodoc:
# = Active Job Queue adapter
#
# The +ActiveJob::QueueAdapter+ module is used to load the
# correct adapter. The default queue adapter is the +:async+ queue.
# correct adapter. The default queue adapter is +:async+,
# which loads the ActiveJob::QueueAdapters::AsyncAdapter.
module QueueAdapter # :nodoc:
extend ActiveSupport::Concern

Expand All @@ -24,22 +25,20 @@ module QueueAdapter # :nodoc:
class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false

delegate :queue_adapter, to: :class

self.queue_adapter = :async
end

# Includes the setter method for changing the active queue adapter.
module ClassMethods
# Returns the backend queue provider. The default queue adapter
# is the +:async+ queue. See QueueAdapters for more information.
# is +:async+. See QueueAdapters for more information.
def queue_adapter
_queue_adapter
with_default_if_no_adapter_set { _queue_adapter }
end

# Returns string denoting the name of the configured queue adapter.
# By default returns <tt>"async"</tt>.
def queue_adapter_name
_queue_adapter_name
with_default_if_no_adapter_set { _queue_adapter_name }
end

# Specify the backend queue provider. The default queue adapter
Expand All @@ -61,6 +60,15 @@ def queue_adapter=(name_or_adapter)
end

private
def with_default_if_no_adapter_set
value = yield
if value.nil?
self.queue_adapter = :async
value = yield
end
value
end

def assign_adapter(adapter_name, queue_adapter)
self._queue_adapter_name = adapter_name
self._queue_adapter = queue_adapter
Expand Down
2 changes: 1 addition & 1 deletion activejob/lib/active_job/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class Railtie < Rails::Railtie # :nodoc:

initializer "active_job.set_configs" do |app|
options = app.config.active_job
options.queue_adapter ||= :async
options.queue_adapter ||= (Rails.env.test? ? :test : :async)

config.after_initialize do
options.each do |k, v|
Expand Down
40 changes: 35 additions & 5 deletions activejob/lib/active_job/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ def enable_test_adapter(test_adapter)
end

def before_setup # :nodoc:
test_adapter = queue_adapter_for_test

queue_adapter_changed_jobs.each do |klass|
klass.enable_test_adapter(test_adapter)
if (test_adapter = test_adapter_should_be(klass))
klass.enable_test_adapter(test_adapter)
end
end

clear_enqueued_jobs
Expand All @@ -61,7 +61,6 @@ def after_teardown # :nodoc:
# Override this method to specify a different adapter. The adapter must
# implement the same interface as ActiveJob::QueueAdapters::TestAdapter.
def queue_adapter_for_test
ActiveJob::QueueAdapters::TestAdapter.new
end

# Asserts that the number of enqueued jobs matches the given number.
Expand Down Expand Up @@ -118,6 +117,8 @@ def queue_adapter_for_test
# end
# end
def assert_enqueued_jobs(number, only: nil, except: nil, queue: nil, &block)
require_active_job_test_adapter!("assert_enqueued_jobs")

if block_given?
original_jobs = enqueued_jobs_with(only: only, except: except, queue: queue)

Expand Down Expand Up @@ -180,6 +181,8 @@ def assert_enqueued_jobs(number, only: nil, except: nil, queue: nil, &block)
#
# assert_enqueued_jobs 0, &block
def assert_no_enqueued_jobs(only: nil, except: nil, queue: nil, &block)
require_active_job_test_adapter!("assert_no_enqueued_jobs")

assert_enqueued_jobs 0, only: only, except: except, queue: queue, &block
end

Expand Down Expand Up @@ -270,6 +273,8 @@ def assert_no_enqueued_jobs(only: nil, except: nil, queue: nil, &block)
# end
# end
def assert_performed_jobs(number, only: nil, except: nil, queue: nil, &block)
require_active_job_test_adapter!("assert_performed_jobs")

if block_given?
original_count = performed_jobs.size

Expand Down Expand Up @@ -338,6 +343,8 @@ def assert_performed_jobs(number, only: nil, except: nil, queue: nil, &block)
#
# assert_performed_jobs 0, &block
def assert_no_performed_jobs(only: nil, except: nil, queue: nil, &block)
require_active_job_test_adapter!("assert_no_performed_jobs")

assert_performed_jobs 0, only: only, except: except, queue: queue, &block
end

Expand Down Expand Up @@ -394,6 +401,8 @@ def assert_no_performed_jobs(only: nil, except: nil, queue: nil, &block)
# end
# end
def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil, priority: nil, &block)
require_active_job_test_adapter!("assert_enqueued_with")

expected = { job: job, args: args, at: at, queue: queue, priority: priority }.compact
expected_args = prepare_args_for_assertion(expected)
potential_matches = []
Expand Down Expand Up @@ -496,6 +505,8 @@ def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil, priority: nil
# end
# end
def assert_performed_with(job: nil, args: nil, at: nil, queue: nil, priority: nil, &block)
require_active_job_test_adapter!("assert_performed_with")

expected = { job: job, args: args, at: at, queue: queue, priority: priority }.compact
expected_args = prepare_args_for_assertion(expected)
potential_matches = []
Expand Down Expand Up @@ -604,7 +615,10 @@ def assert_performed_with(job: nil, args: nil, at: nil, queue: nil, priority: ni
# If queue_adapter_for_test is overridden to return a different adapter,
# +perform_enqueued_jobs+ will merely execute the block.
def perform_enqueued_jobs(only: nil, except: nil, queue: nil, at: nil, &block)
return flush_enqueued_jobs(only: only, except: except, queue: queue, at: at) unless block_given?
unless block_given?
require_active_job_test_adapter!("perform_enqueued_jobs (without a block)")
return flush_enqueued_jobs(only: only, except: except, queue: queue, at: at)
end

return _assert_nothing_raised_or_warn("perform_enqueued_jobs", &block) unless using_test_adapter?

Expand Down Expand Up @@ -646,6 +660,22 @@ def queue_adapter
end

private
def test_adapter_should_be(klass)
if (override_in_test_class = queue_adapter_for_test)
return override_in_test_class
end

if klass._queue_adapter.nil?
ActiveJob::QueueAdapters::TestAdapter.new
end
end

def require_active_job_test_adapter!(method)
unless using_test_adapter?
raise ArgumentError.new("#{method} requires the Active Job test adapter, you're using #{queue_adapter.class.name}.")
end
end

def using_test_adapter?
queue_adapter.is_a?(ActiveJob::QueueAdapters::TestAdapter)
end
Expand Down
4 changes: 4 additions & 0 deletions activejob/test/cases/instrumentation_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ class InstrumentationTest < ActiveSupport::TestCase
end

test "retry emits an enqueue retry event" do
skip if adapter_is?(:inline, :sneakers)

events = subscribed("enqueue_retry.active_job") do
perform_enqueued_jobs { RetryJob.perform_later("DefaultsError", 2) }
end
assert_equal 1, events.size
end

test "retry exhaustion emits a retry_stopped event" do
skip if adapter_is?(:inline, :sneakers)

events = subscribed("retry_stopped.active_job") do
perform_enqueued_jobs { RetryJob.perform_later("CustomCatchError", 6) }
end
Expand Down
6 changes: 6 additions & 0 deletions activejob/test/cases/logging_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ def test_job_no_error_logging_on_rescuable_job
end

def test_enqueue_retry_logging
skip if adapter_is?(:inline, :sneakers)

perform_enqueued_jobs do
RetryJob.perform_later "DefaultsError", 2
assert_match(/Retrying RetryJob \(Job ID: .*?\) after \d+ attempts in 3 seconds, due to a DefaultsError.*\./, @logger.messages)
Expand All @@ -303,13 +305,17 @@ def test_enqueue_retry_logging_on_retry_job
end

def test_retry_stopped_logging
skip if adapter_is?(:inline, :sneakers)

perform_enqueued_jobs do
RetryJob.perform_later "CustomCatchError", 6
end
assert_match(/Stopped retrying RetryJob \(Job ID: .*?\) due to a CustomCatchError.*, which reoccurred on \d+ attempts\./, @logger.messages)
end

def test_retry_stopped_logging_without_block
skip if adapter_is?(:inline, :sneakers)

perform_enqueued_jobs do
RetryJob.perform_later "DefaultsError", 6
rescue DefaultsError
Expand Down
13 changes: 13 additions & 0 deletions activejob/test/cases/queue_adapter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ class QueueAdapterTest < ActiveJob::TestCase
assert_equal base_queue_adapter, child_job_three.queue_adapter, "child_job_three's queue adapter should remain unchanged"
end

test "should default to :async adapter if no adapters are set at all" do
ActiveJob::Base.disable_test_adapter
_queue_adapter_was = ActiveJob::Base._queue_adapter
_queue_adapter_name_was = ActiveJob::Base._queue_adapter_name
ActiveJob::Base._queue_adapter = ActiveJob::Base._queue_adapter_name = nil

assert_equal "async", ActiveJob::Base.queue_adapter_name
assert_kind_of ActiveJob::QueueAdapters::AsyncAdapter, ActiveJob::Base.queue_adapter
ensure
ActiveJob::Base._queue_adapter = _queue_adapter_was
ActiveJob::Base._queue_adapter_name = _queue_adapter_name_was
end

test "should extract a reasonable name from a class instance" do
child_job = Class.new(ActiveJob::Base)
child_job.queue_adapter = ActiveJob::QueueAdapters::StubOneAdapter.new
Expand Down
31 changes: 29 additions & 2 deletions activejob/test/cases/test_case_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,37 @@ def test_include_helper
end

def test_set_test_adapter
assert_kind_of ActiveJob::QueueAdapters::TestAdapter, queue_adapter
# The queue adapter the job uses depends on the Active Job config.
# See https://github.com/rails/rails/pull/48585 for logic.
expected = case ActiveJob::Base.queue_adapter_name.to_sym
when :test
ActiveJob::QueueAdapters::TestAdapter
when :inline
ActiveJob::QueueAdapters::InlineAdapter
when :async
ActiveJob::QueueAdapters::AsyncAdapter
when :backburner
ActiveJob::QueueAdapters::BackburnerAdapter
when :delayed_job
ActiveJob::QueueAdapters::DelayedJobAdapter
when :queue_classic
ActiveJob::QueueAdapters::QueueClassicAdapter
when :resque
ActiveJob::QueueAdapters::ResqueAdapter
when :sidekiq
ActiveJob::QueueAdapters::SidekiqAdapter
when :sneakers
ActiveJob::QueueAdapters::SneakersAdapter
when :sucker_punch
ActiveJob::QueueAdapters::SuckerPunchAdapter
else
raise NotImplementedError.new
end

assert_kind_of expected, queue_adapter
end

def test_does_not_perform_enqueued_jobs_by_default
assert_nil queue_adapter.perform_enqueued_jobs
assert_nil ActiveJob::QueueAdapters::TestAdapter.new.perform_enqueued_jobs
end
end
Loading

0 comments on commit e846921

Please sign in to comment.