RSpec Rails测试:如何强制ActiveJob作业在某些测试中内联运行?

14

我希望在特定标记的测试中内联运行我的后台作业。 我可以通过使用perform_enqueued do包装测试来实现,但是如果可能的话,我希望能够仅使用元数据进行标记并自动完成。

我尝试了以下方法:

it "does everything in the job too", perform_enqueued: true do
end

config.around(:each) do |example|
  if example.metadata[:perform_enqueued]
    perform_enqueued_jobs do
      example.run
    end
  end
end

但会导致出现错误:

undefined method `perform_enqueued_jobs=' for ActiveJob::QueueAdapters::InlineAdapter:Class
3个回答

12

你需要将测试适配器设置为ActiveJob::QueueAdapters::TestAdapter,该适配器响应于.perform_enqueued_jobs=。您可以在spec/rails_helper.rb文件中完成此操作:

ActiveJob::Base.queue_adapter = :test

11
在你的 spec/rails_helper.rb 文件中:
RSpec.configure do |config|
  # ...
  config.include ActiveJob::TestHelper
end

或者在你的测试中:

context "when jobs are executed" do
  include ActiveJob::TestHelper

  # ...
end

然后在您的测试中:

perform_enqueued_jobs do
  example.run
end

1

如何在测试中使用InlineAdapter? — ActiveJob::TestHelper不允许你这样做!

我看到你试图使用InlineAdapter...

我也有同样的愿望——在所有的测试中使用InlineAdapter

不幸的是——至少对于RSpec请求测试(ActionDispatch集成测试)——ActiveJob::TestHelper会自动包含到测试上下文中,并且似乎强制你使用ActiveJob::QueueAdapters::TestAdapter而不是InlineAdapter

gems/activejob-7.0.4.3/lib/active_job/test_helper.rb就是这样:

    ActiveJob::Base.include(TestQueueAdapter)

    def before_setup # :nodoc:
      test_adapter = queue_adapter_for_test

      queue_adapter_changed_jobs.each do |klass|
        klass.enable_test_adapter(test_adapter)
      end

      clear_enqueued_jobs
      clear_performed_jobs
      super
    end

    def after_teardown # :nodoc:
      super

      queue_adapter_changed_jobs.each { |klass| klass.disable_test_adapter }
    end

    # Specifies the queue adapter to use with all Active Job test helpers.
    #
    # Returns an instance of the queue adapter and defaults to
    # ActiveJob::QueueAdapters::TestAdapter.
    #
    # Note: The adapter provided by this method must provide some additional
    # methods from those expected of a standard ActiveJob::QueueAdapter
    # in order to be used with the active job test helpers. Refer to
    # ActiveJob::QueueAdapters::TestAdapter.
    def queue_adapter_for_test
      ActiveJob::QueueAdapters::TestAdapter.new
    end

这会导致它完全忽略您可能拥有的任何config.active_job.queue_adapter = :inline配置(因为它覆盖queue_adapterclass_attribute)。

我甚至尝试重写queue_adapter_for_test

    def queue_adapter_for_test
      ActiveJob::QueueAdapters::InlineAdapter.new
    end

但它仍然无法工作,因为InlineAdapter未定义enqueued_jobs,我们会得到以下错误:

     NoMethodError:
       undefined method `enqueued_jobs' for #<ActiveJob::QueueAdapters::InlineAdapter:0x00007f7efcce6580>
       Did you mean?  enqueue
     # gems/3.1.0/gems/activejob-7.0.4.3/lib/active_job/test_helper.rb:9:in `enqueued_jobs'
     # gems/3.1.0/gems/activejob-7.0.4.3/lib/active_job/test_helper.rb:641:in `clear_enqueued_jobs'
     # gems/3.1.0/gems/activejob-7.0.4.3/lib/active_job/test_helper.rb:46:in `before_setup'

总之... 没有任何支持的方法可以在包含ActiveJob::TestHelper(例如请求测试)的任何地方使用InlineAdapter。但总是有解决办法...
解决方法1:覆盖enqueued_jobs等,以避免出现错误
假设您正在使用RSpec,您可以添加一个spec/support/active_job.rb文件来执行此操作:
# Override some things from ActiveJob::TestHelper (which gets included automatically by RSpec in
# request tests) so that we can use InlineAdapter instead of TestAdapter and don't have to manually
# call perform_enqueued_jobs any time we have jobs that get enqueued — InlineAdapter automatically
# runs the job immediately (synchronously). See
# https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html.
module UseInlineQueueAdapter
  def queue_adapter_for_test
    ActiveJob::QueueAdapters::InlineAdapter.new
  end

  def enqueued_jobs
    if queue_adapter.respond_to?(__callee__)
      super
    else
      []
    end
  end

  def performed_jobs
    if queue_adapter.respond_to?(__callee__)
      super
    else
      []
    end
  end
end

RSpec.configure do |config|
  config.include UseInlineQueueAdapter
end

你的具体问题 - 仅适用于某些标记测试

在我的情况下,我很高兴只是使用内联适配器来进行所有测试...

但是听起来你想要只在一些被元数据标记的测试中使用它的能力。

这似乎不应该更加困难。看起来你所需要做的就是为queue_adapter_for_test方法添加一个条件语句:

  def queue_adapter_for_test
    if example.metadata[:inline_jobs]
      ActiveJob::QueueAdapters::InlineAdapter.new
    else
      ActiveJob::QueueAdapters::TestAdapter.new
    end
  end

解决方法 #2:按照它的要求使用 TestAdapter

perform_enqueued_jobs 会导致在块执行期间排队的任何作业立即执行(“内联”),因此您原本的代码应该也可以正常工作——但前提是您将 queue_adapter 设置为 TestAdapter (:test):

config.around(:each) do |example|
  if example.metadata[:perform_enqueued]
    perform_enqueued_jobs do
      example.run
    end
  end
end

另一个导致你的方法不起作用的原因是因为around(:each)before(:each)之前运行。所以即使ActiveJob::TestHelper自动将队列适配器更改为TestAdapter,它也是从before(:each)(技术上是before_setup回调)中进行的。因此,在你的around(:each)调用perform_enqueued_jobs时,ActiveJob::Base.queue_adapter应该仍然保持配置中的状态。
假设你在config/environments/test.rb中有像config.active_job.queue_adapter = :inline这样的设置?正如其他答案所指出的那样,如果你想让你的方法起作用,你必须将其改为:test
因为正如错误提示的那样,InlineAdapter没有enqueued_jobs的概念,相应地,也没有定义名为perform_enqueued_jobs的方法。
以下是我想出来的解决方案,似乎可以工作:
RSpec.configure do |config|
  config.include(Module.new do
    # Without this, the perform_enqueued_jobs block below has no effect, because it sets
    # perform_enqueued_jobs on ActiveJob::Base.queue_adapter, yet
    # ActiveJob::TestHelper#queue_adapter_for_test by default instantiates a _new_
    # ActiveJob::QueueAdapters::TestAdapter.new (this happens in a before(:example)), whose
    # perform_enqueued_jobs attribute would of course still have the default value of nil.
    def queue_adapter_for_test
      if ActiveJob::Base.queue_adapter.is_a?(ActiveJob::QueueAdapters::TestAdapter)
        ActiveJob::Base.queue_adapter
      else
        super
      end
    end
  end)

  config.around do |example|
    if example.metadata[:perform_enqueued_jobs]
      perform_enqueued_jobs do
        example.run
      end
    else
      example.run
    end
  end
end

现在,您可以继续注释任何示例组或示例,其中您想要立即执行入队作业!

it "performs the job immediately as soon as enqueued", :perform_enqueued_jobs do
  # ...
end

尝试了这两种解决方法后,我现在建议使用第二种,因为它更灵活,仍然可以让您在需要异步排队的任何测试中进行此操作,但是在您可以使用更简单的内联执行选项时,可以切换到该选项...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接