【问题标题】:How to check what is queued in ActiveJob using Rspec如何使用 Rspec 检查 ActiveJob 中排队的内容
【发布时间】: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。

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

【问题讨论】:

    标签: ruby-on-rails ruby rspec rails-activejob


    【解决方案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 .
                猜你喜欢
                • 1970-01-01
                • 2017-02-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2023-04-03
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多