モックとスタブの使い分けを理解しよう
学習の目標
本章では、以下の内容を学習します。
- テストダブル、モック、スタブの違いを理解する
- それぞれの技法が最適な場面を見分ける力を身につける
- テストの目的に合わせた適切なテスト手法を選べるようになる
- 実例を通じてそれぞれの使い方を習得する
はじめに
前回までに、テストで使うモックとスタブという2つの技法について学んできました。似ているようで異なるこの2つですが、実は重複する機能も持っています。そのため、使い分けが難しく感じることもあるかもしれません。
改めてそれぞれの特徴を整理し、どのような場面で使い分けるべきかを学んでいきましょう。テストを書く際、目的に応じて適切なテスト技法を選ぶことができれば、より効果的で理解しやすいテストコードを書くことができます。
戻り値の設定とメソッド呼び出しの検証
スタブとモックでは、テストの目的や書き方が大きく異なります。それぞれの例を見ながら、その違いを理解していきましょう。
まずは、スタブを使ったテストを見てみましょう。以前作った、user_register_spec.rb
の中身を確認してみます。
RSpec.describe UserRegister do describe '#register' do it 'メール送信に成功したら登録も成功する' do # EmailServiceのダブルを作成 email_service = instance_double(EmailService)
# スタブの設定:メソッドが呼ばれた時の戻り値を設定 allow(email_service).to receive(:deliver_welcome_mail).and_return(true)
user_register = UserRegister.new(email_service) result = user_register.register('test@example.com')
# 結果を確認:registerメソッドがtrueを返すことを期待 expect(result).to be true end endend
このテストではinstance_double
で作ったダブルに対して、スタブを使用しています。スタブは「メソッドが呼ばれた時に、特定の値を返すように設定する」という目的で使います。
ここで少し補足説明をしておきましょう。これまでの説明では「モック」という言葉を「テストで使う代わりのもの」という広い意味で使ってきました。しかし、より正確には「モック」と「テストダブル」は異なる概念です。
テストダブルは、テストで使う代わりのオブジェクトそのものを指します。つまり、実際のオブジェクトの代わりに使う偽物のオブジェクトのことです。
このテストダブルには2つの重要な機能を持たせることができます。
スタブは、メソッドが呼ばれた時に「特定の値を返す」機能を提供します。これによりテスト対象のメソッドが期待通りの値を受け取れるようになります。
モックは、「メソッドが本当に呼ばれたかどうかを確認する」機能を持っています。テスト対象のメソッドが他のオブジェクトと正しく連携しているかを検証できます。
先ほどのコードで使っているinstance_double
は、テストダブルを作るためのメソッドです。これは、本物のクラスの定義に沿った安全なテストダブルを作ってくれます。
そして、このテストダブルに対してallow().to receive()
を使ってスタブの機能を追加しています。つまり「このメソッドが呼ばれたら、この値を返してください」という設定をしているわけです。
モックを使用したテスト例
次に、モックを使ったテストを見てみましょう。
RSpec.describe UserRegister do describe '#register' do it 'ユーザー登録時にメールが送信される' do # モックの作成 email_service = instance_double( EmailService, deliver_welcome_mail: true )
# モックの設定:メソッドが呼ばれることを期待 expect(email_service).to receive(:deliver_welcome_mail) .with('test@example.com')
user_register = UserRegister.new(email_service) user_register.register('test@example.com') end endend
モックの場合は少し異なります。expect(email_service).to receive(:deliver_welcome_mail)
という部分に注目してください。
これは**「deliver_welcome_mailメソッドが必ず呼ばれなければならない」**という期待を示しています。さらに、.with('test@example.com')
という部分で「その際の引数は'test@example.com'でなければならない」ということも指定しています。
モックは「メソッドが呼ばれること自体」を検証するために使います。例えば、ユーザー登録処理の中でメール送信メソッドが確実に呼ばれることを確認したい場合に適しています。
モックとスタブの違いを理解する
両者の違いをより具体的に理解するために、ひとつの例で比較してみましょう。
例えば、「ユーザー登録」の機能をテストする場合を考えます。この機能では、ユーザーの情報をデータベースに保存し、その後ウェルカムメールを送信します。
スタブを使ったテストでは、メール送信機能の戻り値(成功したか失敗したか)に応じて、登録処理全体が成功するかどうかをテストします。メール送信が成功したら登録も成功し、メール送信が失敗したら登録も失敗するといった動作を確認します。
モックを使ったテストでは、登録処理の中で本当にメール送信メソッドが呼ばれるかどうかをテストします。これにより、「ユーザー登録時には必ずウェルカムメールが送信される」という仕様が守られているかを確認します。
つまり、スタブは「メソッドの返り値に依存する処理」をテストするのに適しており、モックは「メソッドが確実に呼ばれること」をテストするのに適しています。
モックとスタブの使い分け
それでは、具体的な使い分けについて、改めて整理してみましょう。
スタブを使う場面
スタブは、以下のような場面で特に役立ちます。
テスト対象以外の部分をシンプルに置き換えたい場合に適しています。例えば、データベースアクセスやAPIリクエストなど、テストの本質ではない部分を簡略化できます。
データベースや外部APIの返り値を固定したい場合にも便利です。実際のデータベースやAPIは状態が変わりやすいですが、スタブを使うことで安定したテスト結果を得られます。
「取得する値」に注目してテストを書きたい場合も、スタブが適しています。メソッドから返される値に応じて、テスト対象の挙動が変わるかどうかを確認できます。
モックを使う場面
一方、モックは以下のような場面で効果を発揮します。
オブジェクト間の相互作用をテストしたい場合に適しています。例えば、あるオブジェクトが別のオブジェクトのメソッドを正しく呼び出しているかを確認できます。
「メソッドが呼ばれること」自体を確認したい場合にも便利です。特定の条件下で特定のメソッドが必ず実行されることを保証したい時に役立ちます。
テスト対象が依存クラスを適切に利用していることを検証したい場合も、モックが最適です。例えば、正しい引数でメソッドを呼んでいるかどうかを確認できます。
同時に使うケース
実際のテストでは、スタブとモックを組み合わせて使うこともあります。例えば、あるメソッドに対して「特定の引数で呼ばれること」を検証しつつ、「特定の値を返すこと」も設定するといった使い方です。
RSpec.describe UserRegister do describe '#register' do it 'メール送信に成功すると登録も成功する' do email_service = instance_double(EmailService)
# モックとしての期待:特定の引数で呼ばれること expect(email_service).to receive(:deliver_welcome_mail) .with('test@example.com') # スタブとしての設定:trueを返すこと .and_return(true)
user_register = UserRegister.new(email_service) result = user_register.register('test@example.com')
expect(result).to be true end endend
このようにして、「メソッドが正しく呼ばれていること」と「その結果に応じて処理が正しく行われること」の両方を一度にテストできます。
まとめ
今回学んだように、スタブとモックはそれぞれ得意とする場面が異なります。テストを書く際は、**「今回のテストで何を確認したいのか」**を考え、適切な方を選んでいくことが大切です。
スタブは「メソッドが返す値」に注目したテストに適しており、モックは「メソッドが呼ばれること自体」に注目したテストに適しています。目的に応じて適切に使い分けることで、より効果的なテストを書くことができます。
また、前回学んだように、instance_double
は「テストダブルの一種」として、これらの機能をより安全に提供してくれます。実際のクラスの定義と一致しない使い方をしようとすると、早い段階でエラーにしてくれる便利な機能です。
次の章では、Ruby on Railsアプリケーションのモデルテストについて学びます。実際のアプリケーション開発でどのようにテストを書いていくのか、より実践的な知識を身につけていきましょう。
Basicプランでより詳しく学習
この先のコンテンツを読むにはBasicプラン以上が必要です。より詳細な解説、実践的なサンプルコード、演習問題にアクセスして学習を深めましょう。