简介

Laravel 提供了各种实用的工具与断言,让数据库驱动型应用的测试变得更加轻松。此外,Laravel 的模型工厂(model factories)和数据填充器(seeders)能够让你毫不费力地使用应用的 Eloquent 模型及其关系来创建测试用的数据库记录。我们将在以下文档中详细介绍这些强大的功能。

在每次测试后重置数据库

在深入后续内容之前,我们先来了解一下如何在每次测试结束后重置数据库,以免前一次测试产生的数据干扰后续测试。Laravel 自带的 Illuminate\Foundation\Testing\RefreshDatabase trait 可以帮你完成这一工作。只需在测试类中使用该 trait 即可:

<?php

use Illuminate\Foundation\Testing\RefreshDatabase;

pest()->use(RefreshDatabase::class);

test('basic example', function () {
    $response = $this->get('/');

    // ...
});
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * A basic functional test example.
     */
    public function test_basic_example(): void
    {
        $response = $this->get('/');

        // ...
    }
}

如果数据库结构已经是最新的,Illuminate\Foundation\Testing\RefreshDatabase trait 不会对数据库进行迁移操作,而是在数据库事务中执行测试。因此,未使用该 trait 的测试用例添加到数据库中的记录,在测试结束后可能仍然保留在数据库中。

如果你希望彻底重置数据库,可以使用 Illuminate\Foundation\Testing\DatabaseMigrations 或 Illuminate\Foundation\Testing\DatabaseTruncation trait 来代替。不过,这两个选项的执行速度都明显慢于 RefreshDatabase trait。

模型工厂

在测试时,你可能需要在执行测试前向数据库中插入几条记录。创建这些测试数据时,无需手动指定每一列的值,Laravel 允许你使用Eloquent 模型的模型工厂,为每个模型定义一组默认属性。

若想进一步了解如何创建并使用模型工厂来生成模型,请查阅完整的模型工厂文档。定义好模型工厂后,就可以在测试中使用它来创建模型了:

use App\Models\User;

test('models can be instantiated', function () {
    $user = User::factory()->create();

    // ...
});
use App\Models\User;

public function test_models_can_be_instantiated(): void
{
    $user = User::factory()->create();

    // ...
}

运行数据填充器

如果你想在功能测试过程中使用数据库填充器来填充数据库,可以调用 seed 方法。默认情况下,seed 方法会执行 DatabaseSeeder,而它应当负责执行你定义的所有其他填充器。当然,也可以向 seed 方法传递具体的填充器类名:

<?php

use Database\Seeders\OrderStatusSeeder;
use Database\Seeders\TransactionStatusSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

pest()->use(RefreshDatabase::class);

test('orders can be created', function () {
    // Run the DatabaseSeeder...
    $this->seed();

    // Run a specific seeder...
    $this->seed(OrderStatusSeeder::class);

    // ...

    // Run an array of specific seeders...
    $this->seed([
        OrderStatusSeeder::class,
        TransactionStatusSeeder::class,
        // ...
    ]);
});
<?php

namespace Tests\Feature;

use Database\Seeders\OrderStatusSeeder;
use Database\Seeders\TransactionStatusSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * Test creating a new order.
     */
    public function test_orders_can_be_created(): void
    {
        // Run the DatabaseSeeder...
        $this->seed();

        // Run a specific seeder...
        $this->seed(OrderStatusSeeder::class);

        // ...

        // Run an array of specific seeders...
        $this->seed([
            OrderStatusSeeder::class,
            TransactionStatusSeeder::class,
            // ...
        ]);
    }
}

此外,你还可以指示 Laravel 在使用 RefreshDatabase trait 的每一次测试之前,自动对数据库进行数据填充。要实现这一点,只需在基础测试类中添加 Seed 特性即可:

<?php

namespace Tests;

use Illuminate\Foundation\Testing\Attributes\Seed;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

#[Seed]
abstract class TestCase extends BaseTestCase
{
}

当存在 Seed 特性时,测试会在每次使用 RefreshDatabase trait 之前,先执行 Database\Seeders\DatabaseSeeder 类。当然,你也可以通过在测试类上使用 Seeder 特性来指定要执行的具体填充器:

<?php

namespace Tests\Feature;

use Database\Seeders\OrderStatusSeeder;
use Illuminate\Foundation\Testing\Attributes\Seeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

#[Seeder(OrderStatusSeeder::class)]
class OrderTest extends TestCase
{
    use RefreshDatabase;

    // ...
}

可用断言

Laravel 为你的 Pest 或 PHPUnit 功能测试提供了多个数据库断言方法。下面我们将逐一进行介绍。

assertDatabaseCount

断言数据库中的某张表包含指定数量的记录:

$this->assertDatabaseCount('users', 5);

assertDatabaseEmpty

断言数据库中的某张表不包含任何记录:

$this->assertDatabaseEmpty('users');

assertDatabaseHas

断言数据库中的某张表包含符合给定键值查询条件的记录:

$this->assertDatabaseHas('users', [
    'email' => '[email protected]',
]);

assertDatabaseMissing

断言数据库中的某张表不包含符合给定键值查询条件的记录:

$this->assertDatabaseMissing('users', [
    'email' => '[email protected]',
]);

assertSoftDeleted

可以使用 assertSoftDeleted 方法来断言给定的 Eloquent 模型已被“软删除”:

$this->assertSoftDeleted($user);

assertNotSoftDeleted

可以使用 assertNotSoftDeleted 方法来断言给定的 Eloquent 模型未被“软删除”:

$this->assertNotSoftDeleted($user);

assertModelExists

断言给定的单个模型或模型集合存在于数据库中:

use App\Models\User;

$user = User::factory()->create();

$this->assertModelExists($user);

assertModelMissing

断言给定的单个模型或模型集合不存在于数据库中:

use App\Models\User;

$user = User::factory()->create();

$user->delete();

$this->assertModelMissing($user);

expectsDatabaseQueryCount

可以在测试的开头调用 expectsDatabaseQueryCount 方法,用来指定测试过程中预期执行的数据库查询总次数。如果实际执行的查询次数与预期值不完全一致,测试就会失败:

$this->expectsDatabaseQueryCount(5);

// Test...