Skip to content

模拟

介绍

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

Laravel 提供了用于模拟事件、作业和门面的助手。这些助手主要提供了一个便利层,您无需手动进行复杂的 Mockery 方法调用。当然,您可以自由使用 Mockery 或 PHPUnit 来创建自己的模拟或间谍。

Bus Fake

作为模拟的替代方法,您可以使用 Bus facade 的 fake 方法来防止作业被调度。在使用伪造时,断言是在测试代码执行后进行的:

php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Jobs\ShipOrder;
use Illuminate\Support\Facades\Bus;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    public function testOrderShipping()
    {
        Bus::fake();

        // 执行订单发货...

        Bus::assertDispatched(ShipOrder::class, function ($job) use ($order) {
            return $job->order->id === $order->id;
        });

        // 断言作业未被调度...
        Bus::assertNotDispatched(AnotherJob::class);
    }
}

Event Fake

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

php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Events\OrderShipped;
use App\Events\OrderFailedToShip;
use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    /**
     * 测试订单发货。
     */
    public function testOrderShipping()
    {
        Event::fake();

        // 执行订单发货...

        Event::assertDispatched(OrderShipped::class, function ($e) use ($order) {
            return $e->order->id === $order->id;
        });

        Event::assertNotDispatched(OrderFailedToShip::class);
    }
}

Mail Fake

您可以使用 Mail facade 的 fake 方法来防止邮件发送。然后,您可以断言 mailables 已发送给用户,甚至检查它们接收到的数据。在使用伪造时,断言是在测试代码执行后进行的:

php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    public function testOrderShipping()
    {
        Mail::fake();

        // 执行订单发货...

        Mail::assertSent(OrderShipped::class, function ($mail) use ($order) {
            return $mail->order->id === $order->id;
        });

        // 断言消息已发送给给定用户...
        Mail::assertSent(OrderShipped::class, function ($mail) use ($user) {
            return $mail->hasTo($user->email) &&
                   $mail->hasCc('...') &&
                   $mail->hasBcc('...');
        });

        // 断言邮件未发送...
        Mail::assertNotSent(AnotherMailable::class);
    }
}

Notification Fake

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

php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Notifications\OrderShipped;
use Illuminate\Support\Facades\Notification;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

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

Queue Fake

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

php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Jobs\ShipOrder;
use Illuminate\Support\Facades\Queue;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

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

Storage Fake

Storage facade 的 fake 方法允许您轻松生成一个伪磁盘,结合 UploadedFile 类的文件生成工具,大大简化了文件上传的测试。例如:

php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    public function testAvatarUpload()
    {
        Storage::fake('avatars');

        $response = $this->json('POST', '/avatar', [
            'avatar' => UploadedFile::fake()->image('avatar.jpg')
        ]);

        // 断言文件已存储...
        Storage::disk('avatars')->assertExists('avatar.jpg');

        // 断言文件不存在...
        Storage::disk('avatars')->assertMissing('missing.jpg');
    }
}
lightbulb

默认情况下,fake 方法将删除其临时目录中的所有文件。如果您希望保留这些文件,可以使用“persistentFake”方法。

Facades

与传统的静态方法调用不同,facades 可以被模拟。这提供了比传统静态方法更大的优势,并为您提供了与使用依赖注入相同的可测试性。在测试时,您可能经常希望在控制器中模拟对 Laravel facade 的调用。例如,考虑以下控制器操作:

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 facade 的调用,该方法将返回一个 Mockery 模拟实例。由于 facades 实际上是由 Laravel 服务容器 解析和管理的,因此它们比典型的静态类具有更高的可测试性。例如,让我们模拟对 Cache facade 的 get 方法的调用:

php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

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

        $response = $this->get('/users');

        // ...
    }
}
exclamation

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