web-dev-qa-db-ja.com

ActiveJobのretry_onメソッドをrspecで適切にテストする方法は?

私はこの方法をこの数日間、運のない状態でテストしようとしています。

私ができるもう1つのことは、最後の再試行が行われた後に発生するエラー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)>'

また、ジョブをダブルにして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

それはIS ActiveJobのクラスメソッドであり、私はbyebugターミナルでこれを確認しました。これが私のジョブクラスの場合です。

このテストはうまくいきませんか?これは、クラスが特定の引数を持つクラスメソッドを受け取ることを期待しています。私のbyebugretry_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のすばらしいコミュニティに感謝します!

11

これは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

最初のケースでは、executionsretry_onが実行されるたびに実行、設定、チェックされるActiveJobメソッドです。 10を返すようにモックアップし、Bugsnagを呼び出すことを期待します。 retry_onは、すべてのattemptsが満たされた場合にのみ、ブロックで指定したものを呼び出します。これでうまくいきます。

2番目のケースでは、ジョブインスタンスに対して発生するエラーを模擬します。次に、retry_job(内部でretry_onが呼び出されます)が正しく受信されていることを確認して、正しく動作していることを確認します。次に、perform_later呼び出しをminitestperform_enqueued_jobsブロックで囲み、1日と呼びます。

2

以下は、私、複数のテストケース、および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つ少なくなります。

4
thisismydesign

最初の仕様では

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

クラスメソッドretry_onがクラスの初期化フェーズで呼び出されるため、これは機能しません。つまり、スペックの実行中ではなく、そのクラスをメモリにロードするとき

2番目の仕様では、timecopを使用して機能させようとしましたが、同じ理由で失敗しました

3番目の仕様は比較的現実的ですが、

assert_performed_jobs 3

ブロックを渡さないと動作しません

何かのようなもの

assert_performed_jobs 2 do
  //call jobs from here
end
0
Tachyons

私見あなたは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
0
fabriciofreitag