43

我正在使用 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。

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

4

9 回答 9

54

接受的答案不再对我有用,所以我在评论中尝试了 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
于 2014-12-24T15:34:24.810 回答
46

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

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

ActiveJob 的创建者在他们的单元测试中使用了相同的技术。请参阅GridJob 测试对象

他们在测试中创建了一个 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
于 2014-10-09T10:05:46.143 回答
20

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

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

它还有其他一些细节have_enqueued_job可以让你检查参数和它应该排队的次数。

于 2015-11-15T23:46:00.030 回答
12

使用 RSpec 测试 Rails ActiveJob

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
于 2015-06-22T20:48:06.857 回答
9

有一个新的rspec 扩展,让您的生活更轻松。

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
于 2015-03-17T14:22:06.850 回答
6

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

解决方案 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

于 2021-01-03T09:20:25.383 回答
5

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

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

由于某种原因clear_enqueued_jobs,该after街区的工作对我不起作用,但消息来源显示我们可以执行以下操作: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
于 2017-07-16T23:27:01.430 回答
0

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

YourJob.perform_later
expect(YourJob).to have_been_enqueued

请在ruby​​doc 文档中找到好的示例。

于 2021-12-07T07:49:42.950 回答
0

一个简单的解决方案是

# 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
于 2021-12-09T10:07:42.370 回答