ruby-on-rails 如何使用rspec正确测试ActiveJob的retry_on方法?

ycggw6v2  于 2023-10-21  发布在  Ruby
关注(0)|答案(6)|浏览(109)

过去几天我一直在尝试这种方法,但没有运气。
另一件事我想能够做的是rescue错误,泡沫后,最后重试尝试。
请看我下面的评论和代码片段。
retry_on的源代码也在这里。
下面是示例代码和测试:

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)>'

我也试过把这个任务变成一个double,并重写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社区!

vlju58qv

vlju58qv1#

下面的代码对我来说很好,也适用于多个测试用例和测试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计数的尝试次数减少一次。

gr8qqesn

gr8qqesn2#

这是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调用 Package 在minitestperform_enqueued_jobs块中,并就此结束。

laik7k3q

laik7k3q3#

恕我直言,你应该把ActiveJob的测试留给Rails团队。
您只需要确保您正确配置作业

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
2ekbmq32

2ekbmq324#

虽然我同意@fabriciofreitag的观点,即不需要测试外部库的内部,但我认为确认retry_on块配置正确肯定有价值。这个设置对我来说很有效,而不必担心ActiveJob如何管理重试。

# app/jobs/my_job.rb
retry_on TimeoutError,
         wait: :exponentially_longer,
         attempts: 3

# spec/jobs/my_job_spec.rb
describe "error handling" do
  before do
    ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
  end

  context "when TimeoutError is raised" do
    it "retries timed-out requests" do
      expect(client).to receive(:connect).ordered.and_raise(TimeoutError)
      expect(client).to receive(:connect).ordered.and_call_original
      described_class.perform_now
    end

    it "retries three times before re-raising" do
      expect(client).to receive(:connect)
        .exactly(3).times.and_raise(TimeoutError)
      expect { described_class.perform_now }.to raise_error(TimeoutError)
    end
  end
end
92dk7w1h

92dk7w1h5#

在第一个规范中,

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
41ik7eoe

41ik7eoe6#

我的小型测试解决方案:

class ExecuteJob < ApplicationJob
  RETRY_LIMIT = 30
  RETRY_DELAY = 1.second

  retry_on ExpectedErrorClass, wait: RETRY_DELAY, attempts: RETRY_LIMIT

  def perform
   # your code
  end
end

test 'retries run job' do
  assert_performed_jobs(described_class::RETRY_LIMIT) do
    perform_enqueued_jobs do
      assert_raises ExpectedErrorClass do
        described_class.perform_later
      end
    end
  end
end

相关问题