LaravelはDI(Depedency Injection)によって、必要なインスタンスを生成するようになっています。
インスタンスを注入してくれるこの機能は非常に便利なのですが、テストを書いている際に困ることがあります。
例えば、Laravelにはリクエストをエンドポイントに送信して、レスポンスを受け取るテストを書くことが出来ます。
もしテストの一連の処理の間に外部サービスのAPIを実行するクラスがある場合、テストが実行された際に外部サービスのAPIが実行されることになります。
仮に外部サービスのAPIをテストで実行することを許容したとしても、外部サービスに何かしらのトラブルが発生していた場合、テストが失敗するようになり、テストの結果が外部サービスに依存してしまうことになります。
こうした問題を回避するために、テストで実行させたくない処理をモックするという方法が取られます。
今回はLaravelのDIで生成されるインスタンスを別のクラスに差し替える方法を紹介します。
解説
今回紹介するのは、テスト実行時にDIで生成されるインスタンスをMockeryでモックしたクラスに差し替える方法です。
MockeryはPHPUnitやPHPSpec等のテスティングフレームワークと共に使用されるライブラリです。
テストの内容は、routes/web.phpで設定しているエンドポイントにリクエストを送信し、そのレスポンスを確認する、という内容です。
モックの差し替えはテストコードで定義することになります。
使用したサンプルコード、モックを使用した場合とモックを使用していない場合のテストコードをそれぞれ下記に示します。
サンプルコード
ルーティング
routes/web.php
Route::get('post', 'PostController@index')->name('post.index');
モックしたいクラス
app/Services/PostService.php
namespace App\Services;
class PostService
{
public function fetchPosts()
{
return 1;
}
}
コントローラー
app/Http/Controllers/PostController.php
namespace App\Http\Controllers;
use App\Services\PostService;
class PostController extends Controller
{
public function index(PostService $post_service)
{
$posts = $post_service->fetchPosts();
return response($posts);
}
}
上記のPostControllerのindex()の引数、$post_serviceをモックすることが目標です。
テストコード
モックを使用していない場合
namespace Tests\Feature;
use App\Services\PostService;
use Mockery;
use Tests\TestCase;
class PostControllerTest extends TestCase
{
public function testUserCanViewPosts()
{
$response = $this->get(route('post.index'));
$this->assertEquals(100, $response->getContent());
}
}
上記のテストを実行した場合、PostControllerの$post_serviceにはApp\Services\PostServiceクラスのインスタンスが注入されています。
fetchPosts()は1
を返却するため、上記のテストは失敗します。
それでは次にMockeryでモックしたクラスを注入するようにしてみます。
モックを使用した場合
DIで生成するインスタンスを変更するには、instance()を使用します。
namespace Tests\Feature;
use App\Services\PostService;
use Mockery;
use Tests\TestCase;
class PostControllerTest extends TestCase
{
public function testUserCanViewPosts()
{
$mock = Mockery::mock(PostService::class);
$mock->shouldReceive('fetchPosts')
->once()
->andReturn(100);
$this->instance(PostService::class, $mock);
$response = $this->get(route('post.index'));
$this->assertEquals(100, $response->getContent());
}
}
上記の場合、PostControllerの$post_serviceにはMockeryでモックしたインスタンスが注入されています。
$this->instance()の第1引数にはモックしたいクラスの名前を指定し、第2引数にはMockeryのインスタンスを渡します。