【rails】ほとんど見当たらないdelayed_jobのrspecまとめ
ことの始まり
前回の記事でdelayed_job
を導入してみた。
yasagori-programing.hatenablog.jp
導入したのはいいものの、rspecの記事がほとんど見つからない…
これは非常に困ったので、そのやり方をまとめておく。
テストの考え方
まずは考え方を整理しておく。
今まではcontrollerでメールの送信を行っていたので、当然controllerのrspecでは次のような確認をしていた。
class OrdersController < ApplicationController def submit ~~~ #ここにはorderを保存する処理とかがいるはず OrderMailer.send(order).deliver end end
RSpec.describe OrdersController, type: :controller do describe 'メールが送信できること' do before do allow(OrderMailer).to receive_message_chain(:send, :deliver) end it 'success' do ~~~ # 前処理は省略 expect(OrderMailer.send).to have_received(:deliver).once end it 'fail' do ~~~ # 前処理は省略 expect(OrderMailer.send).not_to have_received(:deliver) end end end
しかし、delayed_job
でメール送信が非同期化されたのでcontrollerではジョブを登録するメソッドが呼び出されたこと確認をすれば良い。
メールの送信に関する確認はジョブのテストで確認することにする。
controllerのrspecでジョブメソッドの呼び出し確認
delayed_job
を使ったcontrollerのコードはこうなっている。
def submit OrderMailDeliverJob.perform_later(order) end
ここからメソッドの呼び出し確認テストに変更する。
RSpec.describe OrdersController, type: :controller do describe 'メール送信ジョブが呼ばれている' do before do allow(OrderMailDeliverJob).to receive(:perform_later) end it 'success' do ~~~ # 前処理は省略 expect(OrderMailDeliverJob).to have_received(:perform_later) end it 'fail' do ~~~ # 前処理は省略 expect(OrderMailDeliverJob).not_to have_received(:perform_later) end end end
jobのrspec
そして難関。jobのrspecのやり方。
事前にjobのコードがどうなってるかを確認。
class OrderMailDeliverJob < ApplicationJob queue_as :default def perform(order) OrderMailer.send(order).deliver_now end end
class ApplicationJob attr_reader :attempt_number RETRY_LIMIT = 3 rescue_from(StandardError) do |e| if retry_limit_exceeded? Bugsnag.notify(e) else # 上限以下ならリトライ retry_job(wait: wait_time) end end def serialize super.merge("attempt_number" => (@attempt_number || 0) + 1) end def deserialize(job_data) super @attempt_number = job_data["attempt_number"] end private def retry_limit_exceeded? @attempt_number >= RETRY_LIMIT end def wait_time attempt_number**2 end end
確認したいのはひとまずメール送信の成功と、リトライ含めて失敗したら通知が飛ぶの2パターン。
下準備としてrails_helper
に次のコードを追加しておく。
RSpec.configure do |config| config.include ActiveJob::TestHelper end
まずはメール送信成功のパターンから。
require 'rails_helper' RSpec.describe OrderMailDeliverJob, type: :job do after do Delayed::Job.delete_all # test用のテーブルを空にしておく end context 'メール送信' do before do allow(OrderMailer).to receive_message_chain(:send, :deliver_now) end it '成功' do expect(OrderMailer.send).to receive(:deliver_now).once OrderMailDeliverJob.perform_later(order) # orderはFactoryBotとかで先に作っておく必要あり end end end
いよいよメール送信失敗のパターン。
確認したいのはリトライが設定回数行われていることと通知が飛んでいること。
context '規定回数メール送信失敗' do let(:retry_count) { ApplicationJob::RETRY_LIMIT } before do allow(OrderMailer).to receive_message_chain(:send, :deliver_now).and_raise(RuntimeError) allow(Bugsnag).to receive(:notify) allow_any_instance_of(ApplicationJob).to receive(:wait_time).and_return(0) end it 'bugsnagに通知' do expect(OrderMailer.send).to receive(:deliver_now).exactly(retry_count).times expect(Bugsnag).to receive(:notify).once OrderMailDeliverJob.perform_later(order) end end
ジョブの登録・実行を自分で設定したい
rails_helper
に追加したActiveJob::TestHelper
は賢いのでperform_laterでジョブの実行までやってくれる。
逆にいうとジョブが実行されてしまうので、ジョブが登録されているかの確認ができない。
そこであえてActiveJob::TestHelper
を使わない場合は自分でジョブの実行まで行う必要がある。
context 'メール送信' do before do allow(OrderMailer).to receive_message_chain(:send, :deliver_now) end it '成功' do expect(OrderMailer.send).to receive(:deliver_now).once # jobが登録されていることの確認 expect { OrderMailDeliverJob.perform_later(order) }.to change(Delayed::Job, :count).by(1) # jobを実行 Delayed::Worker.new.run(Delayed::Job.first) end end context '規定回数メール送信失敗' do let(:retry_count) { ApplicationJob::RETRY_LIMIT } before do allow(OrderMailer).to receive_message_chain(:send, :deliver_now).and_raise(RuntimeError) allow(Bugsnag).to receive(:notify) allow_any_instance_of(ApplicationJob).to receive(:wait_time).and_return(0) end it 'bugsnagに通知' do expect(OrderMailer.send).to receive(:deliver_now).exactly(retry_count).times expect(Bugsnag).to receive(:notify).once OrderMailDeliverJob.perform_later(order) # jobをリトライ回数実行 retry_count.times do job = Delayed::Job.first Delayed::Worker.new.run(job) end end end
まとめ
今回ハマったのはActiveJob::TestHelper
を導入しているのに登録ジョブのカウントを取ろうとしたからだった。
導入しないメリットは例えば「2回失敗したけど、3回目で成功した」みたいなパターンのテストを作ることができる。
とはいえ、そこまでやるのはかなり骨が折れるので、実際は最初に書いたやり方で十分だと思われる。