如何使用 Rspec 检查 ActiveJob 中排队的内容

Posted

技术标签:

【中文标题】如何使用 Rspec 检查 ActiveJob 中排队的内容【英文标题】:How to check what is queued in ActiveJob using Rspec 【发布时间】:2014-12-04 04:17:53 【问题描述】:

我正在开发 Rails API 应用程序中的 reset_password 方法。当这个端点被命中时,一个 ActiveJob 被排队,它将向 Mandrill(我们的事务性电子邮件客户端)发出一个请求。我目前正在尝试编写测试以确保在命中控制器端点时 ActiveJob 正确排队。

def reset_password
  @user = User.find_by(email: params[:user][:email])
  @user.send_reset_password_instructions
end

send_reset_password_instructions 在创建 ActiveJob 之前创建一些 url 等,其代码如下:

class SendEmailJob < ActiveJob::Base
  queue_as :default

  def perform(message)
    mandrill = Mandrill::API.new
    mandrill.messages.send_template "reset-password", [], message
  rescue Mandrill::Error => e
    puts "A mandrill error occurred: #e.class - #e.message"
    raise
  end
end

目前我们没有为 ActiveJob 使用任何适配器,所以我只想通过 Rspec 检查 ActiveJob 是否已排队。

目前我的测试看起来像这样(我正在使用工厂女孩来创建用户):

require 'active_job/test_helper'

describe '#reset_password' do
  let(:user)  create :user 

  it 'should create an ActiveJob to send the reset password email' do
    expect(enqueued_jobs.size).to eq 0
    post :reset_password, user:  email: user.email 
    expect(enqueued_jobs.size).to eq 1
  end
end

现实中一切正常,我只需要创建测试!

我正在使用 ruby​​ 2.1.2 和 rails 4.1.6。

我在网络上的任何地方都看不到有关如何对此进行测试的任何文档或帮助,因此我们将不胜感激!

【问题讨论】:

【参考方案1】:

一个简单的解决方案是

# frozen_string_literal: true

class ApplicationJob < ActiveJob::Base
  # Automatically retry jobs that encountered a deadlock
  # retry_on ActiveRecord::Deadlocked

  # Most jobs are safe to ignore if the underlying records are no longer available
  # discard_on ActiveJob::DeserializationError
  #

  def self.my_jobs
    enqueued_jobs.select|x| x['job_class'] == self.name
  end
end

那么你可以在测试中使用辅助方法my_jobs

require 'rails_helper'

RSpec.describe SendBookingRemindersJob, type: :job do
  describe '.start_time_approaching' do
      let!(:booking)  create :booking  

      it 'schedules 4 jobs' do
        SendBookingRemindersJob.start_time_approaching(booking)
        expect(SendBookingRemindersJob.my_jobs.count).to eq(4)
      end
  end

【讨论】:

【参考方案2】:

我认为使用expect your_code .to have_enqueued_job(YourJob) 的解决方案非常干净,因为它们使用“官方”断言。如果你不喜欢长块传递给expect,你也可以使用:

YourJob.perform_later
expect(YourJob).to have_been_enqueued

请在rubydoc documentation 中找到好的示例。

【讨论】:

【参考方案3】:

在我看来,确保在执行请求时将作业排入队列很重要。 您可以通过以下解决方案做到这一点:

解决方案 1

expect post your_api_here, params: params, headers: headers 
 .to have_enqueued_job(YourJob)
 .with(args)

解决方案 2

expect(YourJob).to receive(:perform_later).once.with(args)
post your_api_here, params: params, headers: headers

【讨论】:

【参考方案4】:

在单元测试中,除了检查队列中的内容之外,还可以依赖 ActiveJob 正常工作,并通过模拟其 api 来验证它是否会被调用。

 expect(MyJob).to receive(:perform_later).once 
 post :reset_password, user:  email: user.email 

ActiveJob 的创建者在他们的单元测试中使用了相同的技术。见GridJob Testobject

他们在测试中创建了一个 testmock GridJob 并覆盖了 perform 方法,因此它只会将作业添加到自定义数组中,他们调用 JobBuffer。最后他们测试,缓冲区是否有作业入队

还可以在一个地方进行集成测试。 ActiveJob test_helper.rb 应该与 minitest 一起使用,而不是与 rspec 一起使用。所以你必须重建它的功能。你可以打电话

expect(ActiveJob::Base.queue_adapter.enqueued_jobs).to eq 1

不需要任何东西

更新 1: 正如评论中所注意到的。 ActiveJob::Base.queue_adapter.enqueued_jobs 只能通过将 queue_adapter 设置为测试模式来工作。

# either within config/environment/test.rb
config.active_job.queue_adapter = :test

# or within a test setup
ActiveJob::Base.queue_adapter = :test

【讨论】:

@mylescc 我能够通过将 test_helper 包含在我的 Rspec.describe 块中来使其正常工作:include ActiveJob::TestHelper ActiveJob::Base.queue_adapter.enqueued_jobs 不再起作用 =( 只要 config.active_job.queue_adapter = :test 在您的 config/environments/test.rb 文件中,针对 ActiveJob::Base.queue_adapter.enqueued_jobs 的测试就可以工作。 expect(ActiveJob::Base.queue_adapter.enqueued_jobs).to eq 1 应该是 expect(ActiveJob::Base.queue_adapter.enqueued_jobs.size).to eq 1,缺少 .size 您使用的示例规范实际上不起作用。期望需要出现在传播调用的方法之前。【参考方案5】:

我遇到了一些问题,可能是因为我没有包含 ActiveJob::TestHelper,但这对我有用...

首先确保您将队列适配器设置为:test,如上面的答案所示。

由于某种原因,after 块中的 clear_enqueued_jobs 工作对我不起作用,但 source 表明我们可以执行以下操作:enqueued_jobs.clear

require 'rails_helper'
include RSpec::Rails::Matchers

RSpec.describe "my_rake_task", type: :rake do

  after do
    ActiveJob::Base.queue_adapter.enqueued_jobs.clear
  end  


  context "when #all task is run" do
    it "enqueues jobs which have been enabled" do
      enabled_count = get_enabled_count
      subject.execute
      expect(ActiveJob::Base.queue_adapter.enqueued_jobs.size).to eq(enabled_count)
    end

    it "doesn't enqueues jobs which have been disabled" do
      enabled_count = get_enabled_count
      subject.execute
      expect(ActiveJob::Base.queue_adapter.enqueued_jobs.size).to eq(enabled_count)
    end
  end

end

【讨论】:

【参考方案6】:

Rspec 3.4 现在已经加入了have_enqueued_job,这使得测试更容易:

it "enqueues a YourJob" do
  expect 
    get :your_action, 
  .to have_enqueued_job(YourJob)
end

have_enqueued_job 还具有其他优点,可让您检查参数及其应排队的次数。

【讨论】:

谁觉得这有用,我用expect .to have_enqueued_job.on_queue('mailers') 来检查已发送的电子邮件。 优秀推荐!【参考方案7】:

Testing Rails ActiveJob with RSpec

class MyJob < ActiveJob::Base
  queue_as :urgent

  rescue_from(NoResultsError) do
    retry_job wait: 5.minutes, queue: :default
  end

  def perform(*args)
    MyService.call(*args)
  end
end

require 'rails_helper'

RSpec.describe MyJob, type: :job do
  include ActiveJob::TestHelper

  subject(:job)  described_class.perform_later(123) 

  it 'queues the job' do
    expect  job 
      .to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :size).by(1)
  end

  it 'is in urgent queue' do
    expect(MyJob.new.queue_name).to eq('urgent')
  end

  it 'executes perform' do
    expect(MyService).to receive(:call).with(123)
    perform_enqueued_jobs  job 
  end

  it 'handles no results error' do
    allow(MyService).to receive(:call).and_raise(NoResultsError)

    perform_enqueued_jobs do
      expect_any_instance_of(MyJob)
        .to receive(:retry_job).with(wait: 10.minutes, queue: :default)

      job
    end
  end

  after do
    clear_enqueued_jobs
    clear_performed_jobs
  end
end

【讨论】:

【参考方案8】:

有一个新的rspec extension 让您的生活更轻松。

require 'rails_helper'

RSpec.describe MyController do
  let(:user)  FactoryGirl.create(:user) 
  let(:params)   user_id: user.id  
  subject(:make_request)  described_class.make_request(params) 

  it  expect  make_request .to enqueue_a(RequestMaker).with(global_id(user)) 
end

【讨论】:

【参考方案9】:

接受的答案不再适合我,所以我在 cmets 中尝试了 Michael H. 的建议,它有效。

describe 'whatever' do
  include ActiveJob::TestHelper

  after do
    clear_enqueued_jobs
  end  

  it 'should email' do
    expect(enqueued_jobs.size).to eq(1)
  end
end

【讨论】:

谢谢,帮助很大。有什么方法可以检查正确的作业是否排队? ActiveJob::TestHelper ist 旨在与 minitest 而非 rspec 一起使用。它的代码充满了assert_equal 等。仅将其包含在一种方法中是个坏主意,恕我直言。这个模块中的方法enqueued_jobs只是ActiveJob::Base.queue_adapter.enqueued_jobs的快捷方式 @bobomoreno ActiveJob::Base.queue_adapter.enqueued_jobs 将允许您访问已排队的特定作业。 你也可以做expect your_action .to change(enqueued_jobs, :size).by n,我喜欢用这个来测试没有工作已经入队,使用n = 0。你甚至不必使用这个方法在套件之后做clear_enqueued_jobs .

以上是关于如何使用 Rspec 检查 ActiveJob 中排队的内容的主要内容,如果未能解决你的问题,请参考以下文章

rspec rails testing:如何强制 ActiveJob 作业内联运行某些测试?

使用 RSpec 测试 DeserializationError

集成测试ActionMailer和ActiveJob

如何在 Rails 控制台中运行 ActiveJob 进行调试?

如何使用 RSpec 检查 JSON 响应?

如何使用 pry 调试器检查 rspec 变量