実は簡単!RSpecでメール&ジョブをモックで華麗にテストする方法
こんにちは!
ラブグラフエンジニアのひろです!
今日は Rspec でメーラーや Job をテストする方法について書いていきます。
はじめに
RSpec を使って Rails アプリケーションのテストを書くとき、メール送信や非同期 Job の処理をテストすることがありますよね。
しかし、実際にメールを送信したり、 Job を実行するとテストの速度が遅くなったり、不安定になることもあります。これを解決するために「モック」を使う方法が有効です。
この記事では、 RSpec でモックを用意する 必要性 と 注意点 を解説し、実際のコード例を使ってメールや Job のテストの書き方を紹介します。
モックを用意する必要性
テストでモックを使用することには、いくつかの具体的な理由があります。
モックを用意しない場合、次のような影響が考えられます。
-
テストの実行速度が遅くなる
- 実際にメールを送信したり、外部の API を呼び出したりする処理が含まれると、テストの実行時間が増えます。例えば、メール送信の処理が数秒かかる場合、それが 100 回テスト内で実行されると、それだけでテストが数百秒かかることになります。
- また、非同期 Job の場合、Job キューのシステムを介して処理がおこなわれるため、Job が完了するのを待つ必要があります。これもテストの実行時間を長くする原因となります。
-
テストの不安定さ
- 外部の要因(ネットワークの状態、外部サービスの稼働状況など)に依存するテストは、不安定になりがちです。たとえば、依存している外部のサーバーがダウンしていたり、ネットワークの遅延が発生している場合、テストが失敗してしまうことがあります。これにより、テストが成功したり失敗したりと、結果が毎回変わってしまい、テストの信頼性が下がります。
- モックを使用すれば、外部リソースに依存することなく、テストの動作を制御できるため、安定して再現性の高いテストが可能になります。
これらの理由から、メール送信や Job、外部APIの使用を含むテストでは、モックを用意することが重要です。
モックを使うことで、テストが安定し、予測可能な結果を得ることができるため、開発効率が向上します。
メール送信のモック
ActionMailer
を使ったメール送信の処理をテストする場合、実際にメールを送信するのではなく、メソッドが正しく呼び出されたかを確認します。
以下のコードは、 ActionMailer::MessageDelivery
のモックを使った例です。
let!(:mailer_double) { instance_double(ActionMailer::MessageDelivery) }
before do
allow(mailer_double).to receive(:deliver_later)
allow(PhotographerMailer).to receive_messages(
order_assigned: mailer_double,
order_designated: mailer_double,
)
end
上記のコードでは、 PhotographerMailer
のメソッド呼び出しが photographer_mailer_double
に置き換えられているため、実際にメールを送信する処理はおこなわれません。
代わりに、メソッドが正しく呼ばれたかどうかを確認できます。
Job のモック
非同期 Job をテストする場合も同様に、実際の Job 実行を避け、モックを使います。以下にその例を示します。
before do
allow(SendEmailForPhotographersJob).to receive(:perform_async).with(order.id)
end
この設定により、 SendEmailForPhotographersJob
の perform_async
が呼び出されたとき、実際には何も実行されません。
代わりに、このメソッドが呼び出されたことだけを確認できます。
実際のテストコード例
ここまでの内容を踏まえ、テストコードがどのようになるのかを紹介します。
(一部省略しています)
RSpec.describe SendOrderCreatedMailJob do
describe "#perform" do
subject { described_class.new.perform(order.id) }
before do
mailer_double = instance_double(ActionMailer::MessageDelivery)
allow(mailer_double).to receive(:deliver_later)
allow(PhotographerMailer).to receive_messages(
order_assigned: mailer_double,
order_designated: mailer_double,
)
allow(SendEmailForPhotographersJob).to receive(:perform_async).with(order.id)
end
...
context "カメラマンがアサインできた場合" do
...
it "カメラマンにアサインメールが送られる" do
subject
expect(PhotographerMailer).to have_received(:order_assigned).once
end
end
context "カメラマンが指名された場合" do
...
it "カメラマンに指名メールが送られる" do
subject
expect(PhotographerMailer).to have_received(:order_designated).once
end
end
context "カメラマンが指名されておらず、アサインもできなかった場合" do
...
it "カメラマン全体にメールを送るための Job が呼ばれる" do
subject
expect(SendEmailWhenPhotographerUnfixedJob).to have_received(:perform_async).with(order.id)
end
end
end
end
コードの解説
-
instance_double(ActionMailer::MessageDelivery)
:-
ActionMailer::MessageDelivery
のインスタンスをモック化し、実際のメール送信を避けるために使用します。
-
-
allow(PhotographerMailer).to receive_messages(...)
:-
PhotographerMailer
のメソッド呼び出しを、モックに置き換えています。この方法により、実際にメール送信の処理はおこなわれません。
-
-
expect(PhotographerMailer).to have_received(...)
:- テストの中で、正しいメソッドが期待通りに呼び出されたかどうかを確認できます。
モックを使う際の注意点
モックは便利なツールですが、使い方を誤ると実際の挙動と乖離したテストになってしまう可能性があります。
モックを利用する際には、次の点に注意してください。
-
モックが実際の挙動を正確に再現しているとは限らない
- モックはあくまでテストのために用意された代用品です。モックを使ったテストがパスしたからといって、本番環境でそのコードが正しく動作するとは限りません。特に、複雑なロジックや外部サービスへ依存している処理では、モックの挙動が実際の動作とずれていることがあります。
-
モックのテストになってしまうリスク
- モックを多用しすぎると、実際には「モックが正しく動いているか」を確認するだけのテストになりがちです。モックに限った話ではありませんが、「テストが通るためのテスト」にならないよう意識しましょう。
-
モックを多用しすぎない
- モックはテスト対象の挙動を外部リソースに依存せずに確認するためのツールです。しかし、すべてをモックにしてしまうと、実際のコードとの乖離が大きくなり、期待通りの動作を確認できなくなってしまいます。モックを使うのは外部リソースや処理時間が長い非同期処理に限定し、ビジネスロジックの重要な部分には実際のオブジェクトを使ったテストも用意しておくと、テストの精度が向上します。
モックを使うことでテストが安定する反面、「本番環境での動作保証にはならない」という点を念頭に、モックをどこまで使うかのバランスを考えることが重要です。
適切にモックを活用しつつ、必要に応じて実際の挙動も確認することで、より信頼性の高いテストが書けるようになります。
まとめ
モックを活用することでテストが安定し、開発効率も向上します。
注意するポイントを忘れず、モックを活用した効率的なテストの書き方を実践してみてください!
Discussion