如何使用rspec正确测试ActiveJob的retry_on方法?

时间:2018-08-09 18:40:20

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

过去几天,我一直在尝试测试这种方法,但是没有运气。

我想做的另一件事是rescue在进行最后的重试尝试后冒出的错误。

请在下面查看我的评论和代码段。

Source code for retry_on is here as well for context.

这是示例代码和测试:

   my_job.rb

   retry_on Exception, wait: 2.hours, attempts: 3 do |job, exception|
   # some kind of rescue here after job.exceptions == 3  
   # then notify Bugsnag of failed final attempt.
   end

   def perform(an_object)
     an_object.does_something
   end

   my_spec.rb
   it 'receives retry_on 3 times' do
     perform_enqueued_jobs do
       expect(AnObject).to receive(:does_something).and_raise { Exception }.exactly(3).times
       expect(MyJob).to receive(:retry_on).with(wait: 2.hours, attempts: 3).exactly(3).times
       MyJob.perform_later(an_object)
     end
     assert_performed_jobs 3
   end

测试失败响应:

      1) MyJob.perform receives retry_on 3 times
         Failure/Error: expect(job).to receive(:retry_on).with(wait: 4.hours, attempts: 3).exactly(3).times

   (MyJob (class)).retry_on({:wait=>2 hours, :attempts=>3})
       expected: 3 times with arguments: ({:wait=>2 hours, :attempts=>3})
       received: 0 times
 # ./spec/jobs/my_job_spec.rb:38:in `block (4 levels) in <top (required)>'
 # ./spec/rails_helper.rb:48:in `block (3 levels) in <top (required)>'
 # ./spec/rails_helper.rb:47:in `block (2 levels) in <top (required)>'

我还尝试过将工作翻倍,并使用retry_on方法,但这也不起作用。

我还尝试使用Timecop来加快等待时间,但测试仍然失败:

           my_spec.rb
   it 'receives retry_on 3 times' do
     perform_enqueued_jobs do
       expect(AnObject).to receive(:does_something).and_raise { Exception }.exactly(3).times
       Timecop.freeze(Time.now + 8.hours) do
         expect(MyJob).to receive(:retry_on).with(wait: 2.hours, attempts: 3).exactly(3).times
       end
       MyJob.perform_later(an_object)
     end
     assert_performed_jobs 3
   end

这是ActiveJob的类方法,我已经在byebug终端中确认了我的工作类别就是这种情况。

这个测试行不通吗?期望类接收带有某些参数的类方法。我将byebug放在retry_on块中时也被命中,因此我知道该方法被多次调用。

这几乎就像是在另一个类上被调用一样,这非常令人困惑,我不认为是这种情况,但是我对这门课很不满意。

我几乎通过将测试从测试retry_on rails逻辑本身分离到测试围绕它的业务逻辑来解决了这个问题。在rails更改retry_on逻辑的情况下,这种方法也更好。

但是,对于多个测试用例,它不起作用。如果您将这种情况用于多个情况,则最后一个测试将失败,并说它执行的工作比预期的要多。

 my_spec.rb
 it 'receives retry_on 3 times' do
   perform_enqueued_jobs do
     allow(AnObject).to receive(:does_something).and_raise { Exception }
     expect(AnObject).to receive(:does_something).exactly(3).times
     expect(Bugsnag).to receive(:notify).with(Exception).once
     MyJob.perform_later(an_object)
   end
   assert_performed_jobs 3
 end

my_job.rb

retry_on Exception, wait: , attempts: 3 do |job, exception|
  Bugsnag.notify(exception)
end

def perform(an_object)
  an_object.does_something
end

对此将提供任何帮助/见解。

也将建议在最大尝试次数后如何处理冒泡异常的建议。我正在考虑在retry_on块内引发错误,然后让discard_on触发所引发的错误。

感谢精彩的Stack Overflow社区!

4 个答案:

答案 0 :(得分:3)

以下内容对我来说很好,也适用于多个测试用例以及测试retry_on块的副作用。

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

  context 'when `MyError` is raised' do
    before do
      allow_any_instance_of(described_class).to receive(:perform).and_raise(MyError.new)
    end

    it 'makes 4 attempts' do
      assert_performed_jobs 4 do
        described_class.perform_later rescue nil
      end
    end

    it 'does something in the `retry_on` block' do
      expect(Something).to receive(:something)

      perform_enqueued_jobs do
        described_class.perform_later rescue nil
      end
    end
  end
end

请注意,如果让异常最后出现时,则需要rescue nil(或某种形式的救援)。

请注意,perform_now不算作“排队工作”。因此,described_class.perform_now的尝试次数减少了assert_performed_jobs的次数。

答案 1 :(得分:2)

这是retry_on所需的规范格式,终于对我有用:

it 'receives retry_on 10 times' do
  allow_any_instance_of(MyJob).to receive(:perform).and_raise(MyError.new(nil))
  allow_any_instance_of(MyJob).to receive(:executions).and_return(10)
  expect(Bugsnag).to receive(:notify)
  MyJob.perform_now(an_object)
end

it 'handles error' do
  allow_any_instance_of(MyJob).to receive(:perform).and_raise(MyError.new(nil))
  expect_any_instance_of(MyJob).to receive(:retry_job)
  perform_enqueued_jobs do
    MyJob.perform_later(an_object)
  end
end

对于第一种情况, executions是一个ActiveJob方法,每次执行retry_on时都会运行,设置和检查该方法。我们模拟它返回10,然后期望它调用Bugsnag。 retry_on仅在满足所有attempts后才调用您在块中提供的内容。这样就行了。

对于第二种情况, 然后模拟该错误以引发作业实例。 接下来,我们检查它是否正确接收了retry_jobretry_on在幕后调用)以确认它在做正确的事情。 然后,将perform_later调用包装在minitest perform_enqueued_jobs块中,并将其命名为一天。

答案 2 :(得分:0)

在第一个规范中

expect(MyJob).to receive(:retry_on).with(wait: 2.hours, attempts:3).exactly(3).times

这将永远无法正常工作,因为将在类初始化阶段调用类方法retry_on,即在将类加载到内存中而不是在执行规范时被调用

在第二个规范中,您尝试使用timecop使其工作,但由于相同的原因仍然失败

第三种规格相对更现实,但是

assert_performed_jobs 3

不通过障碍就不会起作用

类似

assert_performed_jobs 2 do
  //call jobs from here
end

答案 3 :(得分:0)

恕我直言,您应该让Rails团队进行ActiveJob的测试。

您只需要确保正确配置作业

it 'retries the job 10 times with 2 minutes intervals' do
  allow(MyJob).to receive(:retry_on)
  load 'app/path/to/job/my_job.rb'
  expect(MyJob).to have_received(:retry_on)
    .with(
      Exception,
      wait: 2.minutes,
      attempts: 10
    )
end