【今回のお話の環境】
Laravel:12.19.3
pest:3.8.2
mockery:1.6.12
開発中、HogeUseCaseをテストしたいけど、UseCaseの中でnewしているFugaServiceをモックしないとうまくテストが通らない...という壁にぶち当たりました。この問題の解決までの道のりと最終的な解決方法を備忘録的に残します!
【解決までの道のり】
-
chatGPT「overloadをしてみればいいんじゃね?」
-
RuntimeException Could not load mock FugaService, class already exists
というエラーが出てテストが失敗する
-
-
似たような苦しみをしてる人いないかな...
-
同じような苦しみをしているstackoverflowの投稿
- その投稿内でこんなコメントが↓
if it is using the new keyword, it is extremely complex to get over it, I would say impossible... can't you change that code to resolve(Connector::class);
(訳:new
しちゃうとメチャメチャ話が難しくなっちゃうよ。resolve(Connector::class)
を試してみて。)
- その投稿内でこんなコメントが↓
-
同じような苦しみをしているstackoverflowの投稿
-
resolve(Connector::class)
を調べてみる
- "サービスコンテナ"に出会う
サービスコンテナ
- 一言で言うと「指定したインスタンスをいい感じに用意してくれる」もの。
- 参考記事:https://qiita.com/minato-naka/items/afa4b930a2afac23261b
Q:「いい感じ」とは?
A:インスタンスの中で使うインスタンス(今回の例ではHogeUseCaseの中で使っているFugaService)をサービスコンテナ側で用意してくれるので、インスタンスの中身を気にしなくて済む(詳しくは参考記事へ...)
今回の使い方
今回やりたかったのは"HogeUseCaseのテストを中で呼ばれるFugaServiceを意識せずに行う"事でした。
そこで、今回は
- HogeUseCase内でFugaServiceをnewするのではなく
app()->make()
で依存解決する - HogeUseCaseTestにてFugaServiceをモックする
- HogeUseCaseを宣言する前に
app()->instance()
で依存解決の時にモックしたインスタンスを使うようにする - あとは普通にHogeUseCaseを宣言!
コード例
実装側
class HogeUseCase
{
public function __invoke()
{
// 前略
$fugaService = app()->make(FugaService::class);
// 後略
}
}
テスト側
$mock = Mockery::mock(FugaService::class);
$mock->shouldReceive('HogeUseCase内で使ってるインスタンスメソッド')
->andReturn(想定される返し値);
app->instance(FugaService::class, $mock);
$usecase = new HogeUseCase;
インスタンスの中で別インスタンスを使う場面はこれから先も多く、それらをテストする場面もそれに応じて増えていくと思われます。そんな時にサービスコンテナのことを思い出したいですね。