【Laravel】テストで使える!DIのインスタンスをMockeryに差し替える方法

Laravel

LaravelはDI(Depedency Injection)によって、必要なインスタンスを生成するようになっています。

インスタンスを注入してくれるこの機能は非常に便利なのですが、テストを書いている際に困ることがあります。

例えば、Laravelにはリクエストをエンドポイントに送信して、レスポンスを受け取るテストを書くことが出来ます。

もしテストの一連の処理の間に外部サービスのAPIを実行するクラスがある場合、テストが実行された際に外部サービスのAPIが実行されることになります。

仮に外部サービスのAPIをテストで実行することを許容したとしても、外部サービスに何かしらのトラブルが発生していた場合、テストが失敗するようになり、テストの結果が外部サービスに依存してしまうことになります。

こうした問題を回避するために、テストで実行させたくない処理をモックするという方法が取られます。

今回はLaravelのDIで生成されるインスタンスを別のクラスに差し替える方法を紹介します。

スポンサーリンク

解説

今回紹介するのは、テスト実行時にDIで生成されるインスタンスをMockeryでモックしたクラスに差し替える方法です。

MockeryはPHPUnitやPHPSpec等のテスティングフレームワークと共に使用されるライブラリです。

GitHub - mockery/mockery: Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL).
Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to off...

テストの内容は、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のインスタンスを渡します。