模拟
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
。
No Comments