やりたかった事
HogeMailerがあり、HogeDeliveryのインスタンスメソッド(send_email)内でHogeMailerを使ってメールを飛ばしていました。
私は今HogeDeliveryのモデルテストを書いていたのでsend_emailのテストを書こうとしました。しかし、ActionMailer::Base.deliveries.count等は使えません(なぜかは知らないので聞かないで) ので、実際にメールが送信できたのか別手段でテストしようと思ったのが始まり。
前提
class HogeMalier < ActionMailer::Base def send_hoge mail(to: params[:to], template: params[:template],......) end end
class HogeDelivery < ActiveRecord::Base def send_email # 色々処理する # Userクラスからuserを引っ張ったり ... HogeMailer.with(toとかsubjectとか).send_hoge.deliver_now # ここがdeliver_laterの場合もある end end
deliver_laterの場合
deliver_laterはActiveJobを利用して、非同期的にメールを送信します。have_enqueued_jobでメール送信ジョブが登録されたことを確認できます。同時にenqueued_jobsからはその情報を取得することができるので、中身をテストしたい場合はこちらを使います。
describe 'HogeDelivery', type: :model do subject{send_mail}(hoge_delivery.send_email) # 引数に送信先の情報を渡す実装ならここ変更 let!(:hoge_delivery){create(:hoge_delivery)} let!(:user){create(:user)} it 'enqueue a job' do expect { send_mail }.to have_enqueued_job.on_queue("mailers") # ジョブが登録される end it 'use user name for "to" value' do send_mail expect(enqueued_jobs[0][:args][3]["to"]).to eq user.name # ジョブから値を引っ張ってくる end end
deliver_nowの場合
deliver_nowはActiveJobを使用せずに同期的に送信するため、上の方法では送信できているのか確認できません。
しかし、よくよく考えるとこのHogeDeliveryモデルテストで担保すべきは、deliver_nowがしっかり呼ばれることです。deliver_nowはあくまでHogeMailerの責務として考えた場合、スタブを利用してメソッドが呼ばれることのみをテストすれば良さそうです。
describe 'HogeDelivery', type: :model do subject{send_mail}(hoge_delivery.send_email) # 引数に送信先の情報を渡す実装ならここ変更 let!(:hoge_delivery){create(:hoge_delivery)} let!(:user){create(:user)} it "call #deliver_later" do message_delivery = instance_double(ActionMailer::MessageDelivery) allow(HogeMailer).to receive_message_chain(:with, :send_hoge). with(to: to, subject: ..とか色々).with(no_args). and_return(message_delivery) expect(message_delivery).to receive(:deliver_later send_mail end end
しかし、send_email内ではUserクラスから情報を引き出してきたりと、テストすべき部分はあります。そのため、deliver_laterを使っているのであれば、上のような期待した値が格納されているかテストするのも、大事かと思います。
余談
スタブは、メソッドを実行させずに欲しい値を取得するもの、モックは指定のメソッドが実行されたかどうかをチェックするためな感じだそうです。
スタブはallow、モックはexpect。まぁ詳しくは参考サイトを見てみてください。