1、简介
测试 Laravel 应用的时候,你可能还想要“模拟 ”应用的特定状态,以便在测试中不让它们真的执行。例如,测试触发事件的控制器时,你可能想要模拟事件监听器以便它们不在测试期间真的执行。这样的话你就可以只测试控制器的 HTTP 响应,而不必担心事件监听器的执行,因为事件监听器可以在它们自己的测试用例中被测试。
Laravel 开箱为模拟事件、任务以及工厂提供了辅助函数,这些辅助函数主要是在Mockery 之上提供了一个方便的层这样你就不必手动调用复杂的 Mockery 方法。当然,你也可以使用 Mockery 或 PHPUnit 来创建自己的模拟。
2、事件
使用Mocks
如果你在重度使用Laravel的事件系统,可能想要在测试时模拟特定事件。例如,如果你在测试用户注册,你可能不想所有 UserRegistered
事件的处理器都被触发,因为这可能会发送欢迎邮件,等等。
Laravel提供了一个方便的 expectsEvents
方法来验证期望的事件被触发,但同时阻止该事件的其它处理器运行:
<?php use App\Events\UserRegistered; class ExampleTest extends TestCase { /** * Test new user registration. */ public function testUserRegistration() { $this->expectsEvents(UserRegistered::class); // Test user registration... } }
可以使用 doesntExpectEvents
方法来验证给定事件没有被触发:
<?php use App\Events\OrderShipped; use App\Events\OrderFailedToShip; class ExampleTest extends TestCase { /** * Test order shipping. */ public function testOrderShipping() { $this->expectsEvents(OrderShipped::class); $this->doesntExpectEvents(OrderFailedToShip::class); // Test order shipping... } }
如果你想要阻止所有事件运行,可以使用 withoutEvents
方法,当这个方法被调用时,所有事件的监听器都会被模拟:
<?php class ExampleTest extends TestCase{ public function testUserRegistration() { $this->withoutEvents(); // 测试用户注册代码... } }
使用Fakes
作为模拟的可选方案,你可以使用 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); } }
3、任务
使用Mocks
有时候,你可能想要在请求时简单测试控制器分发的指定任务,这允许你孤立的测试路由/控制器——将其从任务逻辑中分离出去,当然,接下来你可以在一个独立测试类中测试任务本身。
Laravel提供了一个方便的 expectsJobs
方法来验证期望的任务被分发,但该任务本身不会被测试:
<?php use App\Jobs\ShipOrder; class ExampleTest extends TestCase { public function testOrderShipping() { $this->expectsJobs(ShipOrder::class); // Test order shipping... } }
注:这个方法只检查通过 DispatchesJobs
trait分发方法或辅助函数 dispatch 分发的任务,并不检查直接通过 Queue::push
分发的任务。
和事件模拟辅助函数一样,你还可以使用 doesntExpectJobs
方法测试一个任务没有被分发:
<?php use App\Jobs\ShipOrder; class ExampleTest extends TestCase { /** * Test order cancellation. */ public function testOrderCancellation() { $this->doesntExpectJobs(ShipOrder::class); // Test order cancellation... } }
或者,你还可以使用 withoutJobs
方法忽略所有分发任务。当该方法在测试方法中被调用时,所有该测试期间被分发的任务都会被废弃:
<?php use App\Jobs\ShipOrder; class ExampleTest extends TestCase { /** * 测试订单取消 */ public function testOrderCancellation() { $this->withoutJobs(); // 测试订单取消... } }
使用Fakes
作为mock的可选方案,你可以使用 Queue
门面的 fake
方法来阻止任务被推送到队列。之后你可以断言任务是否被推送到队列。甚至检查接收的数据,使用fakes的时候,断言会在测试代码执行后进行:
<?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); } }
4、邮件Fakes
你可以使用 Mail
门面的 fake
方法阻止邮件被发送。然后断言邮件是否被发送到用户,甚至可以检查接收的数据,使用fakes的时候,断言会在测试代码执行后进行:
<?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); // 断言邮件未被发送... Mail::assertNotSent(AnotherMailable::class); } }
5、通知Fakes
你可以使用 Notification
门面的 fake 方法来阻止通知被发送,之后断言通知是否被发送给用户,甚至可以检查接收的数据。使用 fakes 的时候,断言会在测试代码执行后进行:
<?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 ); } }
6、门面
不同于传统的静态方法调用,门面可以被模拟。这与传统静态方法相比是一个巨大的优势,并且你可以对依赖注入进行测试。测试的时候,你可能经常想要在控制器中模拟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'); } }
注:不要模拟 Request
门面,取而代之地,可以在测试时传递期望输入到 HTTP 辅助函数如 call
和 post
。