モック
イントロダクション
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 ascall
andpost
when running your test.