イントロダクション

Laravelアプリケーションをテストする場合、特定のテストの間は実際に実行されないように、アプリケーションの明確な一部を「モック」したくなります。たとえば、イベントを発行するコントローラをテストする時に、テストの間は実際に実行したくないので、イベントリスナーをモックしたいと思うことでしょう。これにより、コントローラのHTTPレスポンスだけをテストし、イベントリスナーの実行を心配しなくて済みます。なぜなら、イベントリスナーは自身のテストケースにおいて、テストできるからです。

Laravelにはイベント、ジョブ、ファサードを最初からモックできるヘルパーが準備されています。これらのヘルパーは主にMockery上で動作する便利なレイヤーを提供しているので、複雑なMockeryのメソッドコールを自分で作成する必要はありません。もちろん、MockeryやPHPUnitを使用し、自身のモックやスパイを自由に作成してください。

イベント

モックの使用

Laravelのイベントシステムを多用している場合、テスト中は静かにしてもらうか、特定のイベントモックしたいと思うでしょう。たとえばユーザ登録のテスト中は、たぶんUserRegisteredイベントのハンドラには全部起動してもらいたくないでしょう。なぜなら、"Welcome"メールが送られて…などが起きるからです。

Laravelはイベントハンドラを実行させずに期待するイベントが発行されたことを確認する、便利なexpectsEventsメソッドを用意しています。

<?php

use App\Events\UserRegistered;

class ExampleTest extends TestCase
{
    /**
     * 新ユーザの登録テスト
     */
    public function testUserRegistration()
    {
        $this->expectsEvents(UserRegistered::class);

        // ユーザ登録のテスト…
    }
}

指定したイベントが発行されないことを確認するには、doesntExpectEventsメソッドを使います。

<?php

use App\Events\OrderShipped;
use App\Events\OrderFailedToShip;

class ExampleTest extends TestCase
{
    /**
     * 注文発送のテスト
     */
    public function testOrderShipping()
    {
        $this->expectsEvents(OrderShipped::class);
        $this->doesntExpectEvents(OrderFailedToShip::class);

        // 注文発送のテスト…
    }
}

全イベントハンドラの実行を停止したい場合は、withoutEventsメソッドを使用してください。このメソッドが呼び出されると、全イベントの全リスナーはモックされます。

<?php

class ExampleTest extends TestCase
{
    public function testUserRegistration()
    {
        $this->withoutEvents();

        // ユーザ登録コードのテスト…
    }
}

Fakeの使用

モックの代替として、Eventファサードのfakeメソッドを使い、全イベントリスナーの実行を停止できます。その後に、発行されたイベントをアサートし、受け取ったデータを調べることも可能です。Fakeを使用する場合、テスト対象のコードを実行した後に、アサートを作成してください。

<?php

use App\Events\OrderShipped;
use App\Events\OrderFailedToShip;
use Illuminate\Support\Facades\Event;

class ExampleTest extends TestCase
{
    /**
     * 注文発送のテスト
     */
    public function testOrderShipping()
    {
        Event::fake();

        // 注文の実行コード…

        Event::assertFired(OrderShipped::class, function ($e) use ($order) {
            return $e->order->id === $order->id;
        });

        Event::assertNotFired(OrderFailedToShip::class);
    }
}

ジョブ

モックの使用

時には、アプリケーションへリクエストを作成したら、特定のジョブがディスパッチされるかをテストしたいこともあるでしょう。これによりジョブのロジックについて心配せずに、切り離してルートやコントローラをテストできます。もちろん、それから切り離したテストケースで、ジョブをテストすべきです。

Laravelは便利なexpectsJobsメソッドを用意しており、期待しているジョブがディスパッチされたかを検査できます。しかしジョブ自身は実行されません。

<?php

use App\Jobs\ShipOrder;

class ExampleTest extends TestCase
{
    public function testOrderShipping()
    {
        $this->expectsJobs(ShipOrder::class);

        // 注文発送のテスト…
    }
}

Note: このメソッドはDispatchesJobsトレイトのdispatchメソッドか、dispatchヘルパー関数により起動されたジョブだけを検知します。直接Queue::pushで送られたジョブは検知できません。

イベントモックヘルパのように、doesntExpectJobsメソッドを利用し、ジョブをディスパッチせずにテストできます。

<?php

use App\Jobs\ShipOrder;

class ExampleTest extends TestCase
{
    /**
     * 注文キャンセルのテスト
     */
    public function testOrderCancellation()
    {
        $this->doesntExpectJobs(ShipOrder::class);

        // 注文キャンセルのテスト…
    }
}

もしくは、withoutJobsメソッドを使い、全ディスパッチジョブを無効にできます。あるテストメソッド内でこのメソッドを呼び出すと、そのテストの間にディスパッチされた全ジョブは破棄されます。

<?php

use App\Jobs\ShipOrder;

class ExampleTest extends TestCase
{
    /**
     * 注文キャンセルのテスト
     */
    public function testOrderCancellation()
    {
        $this->withoutJobs();

        // 注文キャンセルのテスト…
    }
}

Fakeの使用

モックの代替として、Queueファサードのfakeメソッドを使い、ジョブがキューされるのを防ぐことができます。その後で、ジョブがキューへ投入されたことをアサートし、受け取ったデータの内容を調べることもできます。Fakeを使う場合は、テスト対象のコードを実行した後で、アサートしてください。

<?php

use App\Jobs\ShipOrder;
use Illuminate\Support\Facades\Queue;

class ExampleTest extends TestCase
{
    public function testOrderShipping()
    {
        Queue::fake();

        // 注文の実行コード…

        Queue::assertPushed(ShipOrder::class, function ($job) use ($order) {
            return $job->order->id === $order->id;
        });

        // 特定のキューへジョブが投入されたことをアサート
        Queue::assertPushedOn('queue-name', ShipOrder::class);

        // ジョブが投入されなかったことをアサート
        Queue::assertNotPushed(AnotherJob::class);
    }
}

MailのFake

Mailファサードのfakeメソッドを使い、メールが送信されるのを防ぐことができます。その後で、Mailableがユーザへ送信されたかをアサートし、受け取ったデータを調べることさえできます。Fakeを使用する場合、テスト対象のコードが実行された後で、アサートしてください。

<?php

use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;

class ExampleTest extends TestCase
{
    public function testOrderShipping()
    {
        Mail::fake();

        // 注文の実行コード…

        Mail::assertSent(OrderShipped::class, function ($mail) use ($order) {
            return $mail->order->id === $order->id;
        });

        // メッセージが指定したユーザに届いたことをアサート
        Mail::assertSentTo([$user], OrderShipped::class);

        // Mailableが送られなかったことをアサート
        Mail::assertNotSent(AnotherMailable::class);
    }
}

NotificationのFake

Notificationファサードのfakeメソッドを使用し、通知が送られるのを防ぐことができます。その後で、通知がユーザへ送られたことをアサートし、受け取ったデータを調べることさえできます。Fakeを使用するときは、テスト対象のコードが実行された後で、アサートを作成してください。

<?php

use App\Notifications\OrderShipped;
use Illuminate\Support\Facades\Notification;

class ExampleTest extends TestCase
{
    public function testOrderShipping()
    {
        Notification::fake();

        // 注文の実行コード…

        Notification::assertSentTo(
            $user,
            OrderShipped::class,
            function ($notification, $channels) use ($order) {
                return $notification->order->id === $order->id;
            }
        );

        // 通知が指定したユーザへ送られたことをアサート
        Notification::assertSentTo(
            [$user], OrderShipped::class
        );

        // 通知が送られなかったことをアサート
        Notification::assertNotSentTo(
            [$user], AnotherNotification::class
        );
    }
}

ファサード

伝統的な静的メソッドの呼び出しと異なり、ファサードはモックできます。これにより伝統的な静的メソッドより遥かなアドバンテージを得られ、依存注入を使用する場合と同じテストビリティを持てます。テスト時、コントローラのLaravelファサード呼び出しを頻繁にモックしたくなります。たとえば、以下のようなコントローラアクションを考えてください。

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;

class UserController extends Controller
{
    /**
     * アプリケーションの全ユーザーリストの表示
     *
     * @return Response
     */
    public function index()
    {
        $value = Cache::get('key');

        //
    }
}

shouldReceiveメソッドを使用し、Cacheファサードへの呼び出しをモックできます。これはMockeryインスタンスを返します。ファサードはLaravelのサービスコンテナにより管理され、依存解決されていますので、典型的な静的クラスよりもかなり高いテスタビリティーを持っています。例としてCacheファサードへのgetメソッド呼び出しをモックしてみましょう。

<?php

class FooTest extends TestCase
{
    public function testGetIndex()
    {
        Cache::shouldReceive('get')
                    ->once()
                    ->with('key')
                    ->andReturn('value');

        $this->visit('/users')->see('value');
    }
}

Note: You should not mock the Request facade. Instead, pass the input you desire into the HTTP helper methods such as call and post when running your test.