背景
例えばTestModel::findOrFail()
とか中でしているメソッドのユニットテストを書くと、どうしてもDBが絡んでユニットテストではなくなってしまう。
これの解決方法。
結論
ExampleService
use App\ExampleModel;
class ExampleService {
private $exampleModel;
public function __construct(ExampleModel $exampleModel)
{
$this->exampleModel = $exampleModel;
}
public function func()
{
...
$this->exampleModel->findOrfail($id);
...
}
}
ExampleServiceTest
use App\ExampleModel;
...
$exampleModel = Mockery::mock('\App\ExampleModel')
$entity = new stdClass;
$entity->name = $name;
$exampleModel
->shouldReceive('findOrFail')
->with($id)
->andReturn($entity);
app()->instance(ExampleModel::class, $exampleModel);
...
解説
EloquentModelはコンストラクタインジェクションで入れておいて、builder機能をインスタンスメソッドで使う
例えばfindOrFail()
メソッドなんかはよくstaticメソッドとして実行されるが、内部的には(new static)->$method()
とかやってるだけなので、インスタンスメソッドとして実行しても同じものが得られる。
それであればテストの観点からコンストラクタインジェクションで外部からオブジェクトをもらった方が良い。
テストにおいてEloquentModelのクラス名でサービスコンテナに入れておく
Laravelのコンストラクタインジェクションは、そのクラス名でサービスコンテナに探し行く。つまり内部的にはapp()->make(ExampleModel::class)
で取ってきていると考えられる。
そうであれば、そのクラス名でmockをサービスコンテナに登録しておけば、テスト時にDBが絡むことを防ぐことが出来る。