Skip to content

模拟

介绍

在测试 Laravel 应用程序时,您可能希望“模拟”应用程序的某些方面,以便在给定测试期间不实际执行它们。例如,在测试触发事件的控制器时,您可能希望模拟事件监听器,以便在测试期间不实际执行它们。这使您可以仅测试控制器的 HTTP 响应,而不必担心事件监听器的执行,因为事件监听器可以在自己的测试用例中进行测试。

Laravel 提供了用于模拟事件、任务和门面的助手。这些助手主要提供了一个便捷层,覆盖了 Mockery,因此您不必手动进行复杂的 Mockery 方法调用。当然,您可以自由使用 Mockery 或 PHPUnit 来创建自己的模拟或间谍。

事件

使用模拟

如果您大量使用 Laravel 的事件系统,您可能希望在测试时静音或模拟某些事件。例如,如果您正在测试用户注册,您可能不希望所有 UserRegistered 事件的处理程序都被触发,因为监听器可能会发送“欢迎”电子邮件等。

Laravel 提供了一个方便的 expectsEvents 方法,它验证预期的事件被触发,但阻止这些事件的任何监听器执行:

php
<?php

use App\Events\UserRegistered;

class ExampleTest extends TestCase
{
    /**
     * 测试新用户注册。
     */
    public function testUserRegistration()
    {
        $this->expectsEvents(UserRegistered::class);

        // 测试用户注册...
    }
}

您可以使用 doesntExpectEvents 方法来验证给定的事件未被触发:

php
<?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
<?php

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

        // 测试用户注册代码...
    }
}

使用伪造

作为模拟的替代方案,您可以使用 Event 门面的 fake 方法来阻止所有事件监听器执行。然后,您可以断言事件被触发,甚至检查它们接收到的数据。在使用伪造时,断言是在执行被测试代码之后进行的:

php
<?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
<?php

use App\Jobs\ShipOrder;

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

        // 测试订单发货...
    }
}
exclamation

此方法仅检测通过 DispatchesJobs trait 的调度方法或 dispatch 辅助函数调度的任务。它不检测直接发送到 Queue::push 的队列任务。

与事件模拟助手类似,您还可以使用 doesntExpectJobs 方法测试任务未被调度:

php
<?php

use App\Jobs\ShipOrder;

class ExampleTest extends TestCase
{
    /**
     * 测试订单取消。
     */
    public function testOrderCancellation()
    {
        $this->doesntExpectJobs(ShipOrder::class);

        // 测试订单取消...
    }
}

或者,您可以使用 withoutJobs 方法忽略所有调度的任务。当在测试方法中调用此方法时,测试期间调度的所有任务将被丢弃:

php
<?php

use App\Jobs\ShipOrder;

class ExampleTest extends TestCase
{
    /**
     * 测试订单取消。
     */
    public function testOrderCancellation()
    {
        $this->withoutJobs();

        // 测试订单取消...
    }
}

使用伪造

作为模拟的替代方案,您可以使用 Queue 门面的 fake 方法来阻止任务被排队。然后,您可以断言任务被推送到队列,甚至检查它们接收到的数据。在使用伪造时,断言是在执行被测试代码之后进行的:

php
<?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 方法来阻止邮件发送。然后,您可以断言 邮件 被发送给用户,甚至检查它们接收到的数据。在使用伪造时,断言是在执行被测试代码之后进行的:

php
<?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);
    }
}

通知伪造

您可以使用 Notification 门面的 fake 方法来阻止通知发送。然后,您可以断言 通知 被发送给用户,甚至检查它们接收到的数据。在使用伪造时,断言是在执行被测试代码之后进行的:

php
<?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
<?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
<?php

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

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

您不应模拟 Request 门面。相反,在运行测试时,将所需的输入传递给 HTTP 辅助方法,如 callpost。同样,不要模拟 Config 门面,而是在测试中简单地调用 Config::set 方法。