概要
オブジェクトの中にある オブジェクトのプロパティをモック化する方法を簡単にまとめておく。
前提
下記のようなメインコードがあったとする。(UserService
はサービスコンテナに登録されているものとする)
これはmakeHogeObject()
の戻り値でHogeオブジェクトが返され、Hogeオブジェクトの中のpiyoオブジェクトの中のnameプロパティにアクセスしている処理だ。
$hogeObject = $this->userService->makeHogeObject();
$name = $hogeObject->piyo->name;
内容
上記のコードが含まれるFeatureテストを書きたい、さらにUserService
のメソッド実行をモック化したい場合を想定する。(本来Featureテストは、いわゆる「通しのテスト」なのでサービス層のメソッドも含めて動作を確認する場合がほとんどだが、説明の便宜上モック化している。)
まずは下記のように記載してUserService
をモック化し、サービスコンテナに登録する。
$userServiceMock = Mockery::mock(UserService::class)
$this->app->instance(UserService::class, $userServiceMock);
その後、makeHogeObject()
の戻り値をモック化したいので下記のように追記するだろう。ただ書いていく上で、戻り値が若干厄介であることに気がつく。戻り値は「nameプロパティを含む、piyoオブジェクトを含む、Hogeオブジェクト」である必要がある。
オブジェクトが入れ子になっているのでnew Hoge()
するだけではだめである。
$userServiceMock = Mockery::mock(UserService::class)
$userServiceMock->shouldReceive('makeHogeObject')->andReturn(/** 戻り値どうしよう、、 */);
$this->app->instance(UserService::class, $userServiceMock);
そこで下記のように書いてみよう。
$userS8erviceMock = Mockery::mock(UserService::class)
$piyoObject = new stdClass(); // stdClassをインスタンス化
$piyoObject->name = 'ぴよぴよ'; // nameプロパティを設定
$hogeObject = new Hoge(); // Hogeオブジェクト作成
$hogeObject->piyo = $piyoObject; // HogeオブジェクトにstdClassから作ったPiyoオブジェクトを格納
$userServiceMock->shouldReceive('makeHogeObject')->andReturn($hogeObject); // お膳立てされたHogeオブジェクトを返却
$this->app->instance(UserService::class, $userServiceMock);
※stdClassについてはこちら↓
複雑に見えるが、これでお膳立てはできたっぽい。
余談
実は$hogeObject = new Hoge(); // Hogeオブジェクト作成
の部分は本物のHogeクラスをインスタンス化しなくても良い。下記のような書き方もできる。
$userS8erviceMock = Mockery::mock(UserService::class)
$piyoObject = new stdClass(); // stdClassをインスタンス化
$piyoObject->name = 'ぴよぴよ'; // nameプロパティを設定
$hogeObject = Mockery::mock(Hoge::class); // Hogeオブジェクト作成
$hogeObject->piyo = $piyoObject; // HogeオブジェクトにstdClassから作ったPiyoオブジェクトを格納
$userServiceMock->shouldReceive('makeHogeObject')->andReturn($hogeObject); // お膳立てされたHogeオブジェクトを返却
$this->app->instance(UserService::class, $userServiceMock);
しかし、Hogeクラスがライブラリにて用意されたクラスだと、does not exist on this mock object
などのエラーが出る時がある。
これはHogeクラスに静的メソッドや静的プロパティが定義されていて、それが処理の一貫で外部からのアクセスを受けたりすると出るエラーである。(モックとして側だけHogeオブジェクトを作成しているので静的メソッドなどが定義されていない。ようは呼び出そうとしたメソッドやプロパティが無いことで怒られている。)
その場合は今回紹介した方法で地道にお膳立てをするか、下記のようにすることで解決することもある。(overloadを使う方法)下記の方法は若干無理矢理感がある気がして自分は今回紹介した方法が好みだ。
$userS8erviceMock = Mockery::mock(UserService::class)
$piyoObject = new stdClass(); // stdClassをインスタンス化
$piyoObject->name = 'ぴよぴよ'; // nameプロパティを設定
$hogeObject = Mockery::mock('overload:' . Hoge::class); // Hogeオブジェクト作成
$hogeObject->piyo = $piyoObject; // HogeオブジェクトにstdClassから作ったPiyoオブジェクトを格納
$userServiceMock->shouldReceive('makeHogeObject')->andReturn($hogeObject); // お膳立てされたHogeオブジェクトを返却
$this->app->instance(UserService::class, $userServiceMock);