モックとスタブの違い、依存を差し替えて何を検証するか
スタブとモックとは、どちらもテストで依存を偽物に差し替える道具ですが、スタブは決められた値を返す係、モックは正しく呼ばれたかを検証する係、という役割の違いがあります。
スタブは「値を返す係」
テストしたいクラスが外部のAPIやDBに依存していると、そのまま動かすのは面倒です。そこで依存を偽物に差し替えるわけですが、スタブとモックは目的が違います。スタブは「決められた値を返すだけ」の偽物で、テスト対象に都合のいい入力を与えるために使います。返り値を固定して、対象の振る舞いだけに集中する感じですね。
$repo = $this->createStub(UserRepository::class);
$repo->method('find')->willReturn(new User('太郎'));
$service = new GreetingService($repo);
$this->assertSame('こんにちは太郎さん', $service->greet(1));
ここで関心があるのは GreetingService の出力であって、repo がどう呼ばれたかは問いません。だからスタブで十分です。
モックは「呼ばれ方を検証する係」
一方モックは、その依存が「正しく呼ばれたか」自体を検証したいときに使います。expects で呼び出し回数や引数を宣言しておき、期待どおり呼ばれなければテストが落ちます。値を返すことより、相互作用そのものが検証対象になるわけですね。
$mailer = $this->createMock(Mailer::class);
$mailer->expects($this->once())
->method('send')
->with('taro@example.com');
$service = new SignupService($mailer);
$service->register('taro@example.com');
// send が1回、正しい宛先で呼ばれたかを検証
どちらを使うか迷ったら関心で決める
私の目安は、「返り値を使いたいだけ」ならスタブ、「呼ばれたことが仕様の一部」ならモック、です。メール送信や課金のように「呼んだこと自体が結果」な副作用はモック向き、データ取得のように入力を用意したいだけならスタブ向き、という切り分けですね。何でもモックにすると、検証したいことがぼやけがちです。
// 値がほしいだけ → willReturn(スタブ的な使い方)
$repo->method('find')->willReturn($user);
// 呼ばれ方が仕様 → expects(モック的な使い方)
$logger->expects($this->never())->method('error');
まとめ
createMock でも willReturn だけ使えば実質スタブになるので、道具としては地続きです。大事なのは「このテストで確かめたいのは出力か、それとも呼び出しか」を先に決めることだと思います。そこが決まれば expects を付けるかどうかも自然に決まる気がします。相互作用の検証は強力ですが、やりすぎると実装に縛られたテストになるので、ほどほどにしておきたいですね。
よくある質問
Q. スタブとモックはどう使い分けますか?
A. 返り値を使いたいだけならスタブ、呼ばれたこと自体が仕様ならモックです。メール送信や課金のような副作用はモック、データ取得はスタブ、と関心で決めると迷いません。
Q. 何でもモックにするとよくないのですか?
A. expects で呼び出しを縛りすぎると実装に依存した壊れやすいテストになりがちです。検証したいのが出力か呼び出しかを先に決め、必要な分だけ使うのが無難です。