Mocking
简介
在 Laravel 应用程序测试中,你可能希望「模拟」应用程序的某些功能的行为,从而避免该部分在测试中真正执行。例如:在控制器执行过程中会触发事件,您可能希望模拟事件监听器,从而避免该事件在测试时真正执行。这允许你在仅测试控制器 HTTP 响应的情况时,而不必担心触发事件,因为事件侦听器可以在它们自己的测试用例中进行测试。
Laravel 针对事件、任务和 Facades 的模拟,提供了开箱即用的辅助函数。这些函数基于 Mocker 封装而成,使用非常方便,无需手动调用复杂的 Mockery 函数。
模拟对象
当模拟一个对象将通过 Laravel 的 服务容器 注入到应用中时,你将需要将模拟实例作为 instance
绑定到容器中。这将告诉容器使用对象的模拟实例,而不是构造对象的真身:
use App\Service;
use Mockery;
use Mockery\MockInterface;
public function test_something_can_be_mocked()
{
$this->instance(
Service::class,
Mockery::mock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
})
);
}
为了让以上过程更加便捷,你可以使用 Laravel 的基本测试用例类提供 mock
方法:
use App\Service;
use Mockery\MockInterface;
$mock = $this->mock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
});
当你只需要模拟对象的几个方法时,可以使用 partialMock
方法。 未被模拟的方法将在调用时正常执行:
use App\Service;
use Mockery\MockInterface;
$mock = $this->partialMock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
});
同样,如果你想侦查一个对象,Laravel 的基本测试用例类提供了一个便捷的 spy 方法作为 Mockery::spy
的替代方法,但是,spy会记录spy与被测试代码之间的任何交互,从而允许您在执行代码后做出断言:
use App\Service;
$spy = $this->spy(Service::class);
// ...
$spy->shouldHaveReceived('process');
Facades模拟
与传统静态方法调用不同的是,facades (including real-time facades) 也可以被模拟。相较传统的静态方法而言,它具有很大的优势,即便你使用依赖注入,可测试性不逊半分。在测试中,你可能想在控制器中模拟对 Laravel Facade 的调用。比如下面控制器中的行为:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
/**
* 显示该应用程序的所有用户的列表.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$value = Cache::get('key');
//
}
}
我们可以使用 shouldReceive
方法模拟对 Cache
Facade 的调用,该方法将返回一个 Mockery 模拟的实例。由于 Facades 实际上是由 Laravel 服务容器 解析和管理的,因此它们比传统的静态类具有更好的可测试性。例如,让我们模拟对 Cache
Facade 的 get
方法的调用:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Cache;
use Tests\TestCase;
class UserControllerTest extends TestCase
{
public function testGetIndex()
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$response = $this->get('/users');
// ...
}
}
注意:你不应该模拟 Request
facade。相反,在运行测试时将您想要的输入传递到 HTTP 测试方法 中,例如 get
和 post
。同样,不要模拟 Config
facade,而是在测试中调用 Config::set
方法。
Facade Spies
如果你想 spy 一个 facade,你可以在相应的 facade 上调用 spy
方法。spy 类似于模拟;但是,spy 记录 spy 和被测试代码之间的所有交互,允许你在代码执行后做出断言:
use Illuminate\Support\Facades\Cache;
public function test_values_are_be_stored_in_cache()
{
Cache::spy();
$response = $this->get('/');
$response->assertStatus(200);
Cache::shouldHaveReceived('put')->once()->with('name', 'Taylor', 10);
}
Bus Fake
在测试分发任务的代码时,您通常希望断言已分发给定任务,但实际不进入队列或执行任务。这是因为任务的执行通常可以在单独的测试类中进行测试。
您可以使用 Bus
facade 的 fake
方法来防止将任务分发到队列。然后,在执行测试代码后,您可以使用 assertDispatched
和 assertNotDispatched
方法检查应用试图分发的任务:
<?php
namespace Tests\Feature;
use App\Jobs\ShipOrder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Bus;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_orders_can_be_shipped()
{
Bus::fake();
// 执行订单发货……
// 断言任务被分发……
Bus::assertDispatched(ShipOrder::class);
// 断言任务没有被分发
Bus::assertNotDispatched(AnotherJob::class);
}
}
您可以将闭包传递给 assertDispatched
或 assertNotDispatched
方法,以断言已分发的任务通过了给定的「真实性测试」。如果至少分发了一个通过给定真实性测试的任务,则断言将成功。例如,您可能希望声明已为特定订单分发任务:
Bus::assertDispatched(function (ShipOrder $job) use ($order) {
return $job->order->id === $order->id;
});
任务链
Bus
facade 的 assertChained
方法可用于断言 任务链 已被调度。 assertChained
方法接受一个链式任务数组作为它的第一个参数:
use App\Jobs\RecordShipment;
use App\Jobs\ShipOrder;
use App\Jobs\UpdateInventory;
use Illuminate\Support\Facades\Bus;
Bus::assertChained([
ShipOrder::class,
RecordShipment::class,
UpdateInventory::class
]);
如上例所示,链式任务的数组可是任务类名的数组。但是,您也可以提供实际任务实例的数组。执行此操作时,Laravel 将确保任务实例属于同一类,并且具有与应用分发的任务链相同的属性值:
Bus::assertChained([
new ShipOrder,
new RecordShipment,
new UpdateInventory,
]);
任务批处理
Bus
facade 的 assertBatched
方法可以用来断言 批量任务 被分发。提供给 assertBatched
方法的闭包接收一个 Illuminate\Bus\PendingBatch
的实例,它可用于检查批处理中的任务:
use Illuminate\Bus\PendingBatch;
use Illuminate\Support\Facades\Bus;
Bus::assertBatched(function (PendingBatch $batch) {
return $batch->name == 'import-csv' &&
$batch->jobs->count() === 10;
});