11
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Mockery の shouldReceive() と shouldHaveReceived() ってどう違うの?

Posted at

PHP のテストで DOC1 をテストダブルに置き換えるのに Mockery というライブラリを重宝しています。
そのダミーオブジェクトのメソッド shouldReceive()shouldHaveReceived() の違いが毎度毎度分からなくなるので、整理してみました。

TL;DR

  • shouldReceive() はモックが持つメソッド。 SUT2 の実行前に記述し、期待する間接出力3を定義する。
  • shouldHaveReceived() はスパイが持つメソッド。 SUT の実行後に記述し、間接出力の内容を検証する。

モックとスパイについて

こちらの記事が大変参考になります。
記事下部の分類方法を引用します。

Test Doubleの分類方法は以下のような感じです。

  • テストの範囲内で本物と同じように動作するTest DoubleはFake Object。
  • 内部のパラメータや状態がなんでもあってもテストに影響を及ぼさない代替オブジェクトなら、Dummy Object
  • 上記以外で、テスト対象の間接出力を受け取り、かつ自身でその検証を行うTest DoubleはMock Object
  • 上記以外で、テスト対象の間接出力を受け取りそれをあとから参照可能にするTest DoubleはTest Spy
  • 上記以外で、テスト対象の間接入力を操作できるTest DoubleはTest Stub

モックもスパイも間接出力を受け取る点は同じですが、テストコード上では期待結果や検証内容を記述する場所が SUT の実行前か実行後かという点が特に異なります。

shouldReceive()shouldHaveReceived() の違い

これを踏まえた上で shouldReceive()shouldHaveReceived() のユースケースを見てみます。
公式ドキュメントのサンプルコードを引用します。

$mock = \Mockery::mock('MyClass');
$spy = \Mockery::spy('MyClass');

$mock->shouldReceive('foo')->andReturn(42);

$mockResult = $mock->foo();
$spyResult = $spy->foo();

$spy->shouldHaveReceived()->foo();

var_dump($mockResult); // int(42)
var_dump($spyResult); // null

ここでダミーオブジェクトによって置き換えられるのは MyClass のメソッド foo() です。
$mock->shouldReceive('foo')foo() の実行前に記述され、 foo() の実行を期待される動作として定義します。
また andReturn() で間接入力も操作しており、スタブとしても振る舞うことが可能です。

$spy->shouldHaveReceived()->foo() はメソッド実行後に記述され、 foo() が実行されたかどうか後から検証します。

このように生成されたダミーオブジェクトの振る舞いと前述のモックとスパイの分類が整合することが分かります。

それぞれの使いどころ

\Mockery::mock() は間接入力を操ることができるため使い勝手は良いですが、期待される全てのメソッド呼び出しについて shouldReceive() を定義する必要があります。

そこでスパイです。
再び公式ドキュメントからスパイに関する記述を引用します。

The third type of test doubles Mockery supports are spies. The main difference between spies and mock objects is that with spies we verify the calls made against our test double after the calls were made. We would use a spy when we don’t necessarily care about all of the calls that are going to be made to an object.

A spy will return null for all method calls it receives. It is not possible to tell a spy what will be the return value of a method call. If we do that, then we would deal with a mock object, and not with a spy.

\Mockery::spy() は後からメソッド呼び出しを検証する性質上あらかじめ期待結果を定義する必要がなく、関心のある間接出力についてのみ shouldHaveReceived() で検証する使い方が可能です。
ただし \Mockery::mock() のように間接入力を操作することはできず、メソッドの返り値は全て null になります。

まとめ

テストダブルの概念と Mockery における shouldReceive()shouldHaveReceived() の関係について整理しました。
より詳しい解説が公式ドキュメントに記載されている4ので、読んでみるとまだ色々と発見がありそうです。

  1. DOC at XUnitPatterns.com

  2. SUT at XUnitPatterns.com

  3. indirect output at XUnitPatterns.com

  4. 日本語版のドキュメント もあるようです。

11
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?