Skip to content

数据库测试

介绍

Laravel 提供了多种有用的工具,使测试数据库驱动的应用程序变得更加容易。首先,您可以使用 assertDatabaseHas 辅助函数来断言数据库中存在与给定条件匹配的数据。例如,如果您想验证 users 表中是否存在 email 值为 sally@example.com 的记录,可以执行以下操作:

php
public function testDatabase()
{
    // 调用应用程序...

    $this->assertDatabaseHas('users', [
        'email' => 'sally@example.com'
    ]);
}

您还可以使用 assertDatabaseMissing 辅助函数来断言数据库中不存在数据。

当然,assertDatabaseHas 方法和其他类似的辅助函数是为了方便。您可以自由使用 PHPUnit 的任何内置断言方法来补充您的测试。

在每个测试后重置数据库

在每个测试后重置数据库通常是有用的,以防止前一个测试的数据干扰后续测试。

使用迁移

重置数据库状态的一种方法是在每个测试后回滚数据库,并在下一个测试之前迁移它。Laravel 提供了一个简单的 DatabaseMigrations trait,可以自动为您处理这一切。只需在您的测试类中使用该 trait,一切都会为您处理:

php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    use DatabaseMigrations;

    /**
     * 一个基本的功能测试示例。
     *
     * @return void
     */
    public function testBasicExample()
    {
        $response = $this->get('/');

        // ...
    }
}

使用事务

重置数据库状态的另一种方法是将每个测试用例包装在一个数据库事务中。同样,Laravel 提供了一个方便的 DatabaseTransactions trait,可以自动为您处理这一切:

php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    use DatabaseTransactions;

    /**
     * 一个基本的功能测试示例。
     *
     * @return void
     */
    public function testBasicExample()
    {
        $response = $this->get('/');

        // ...
    }
}
exclamation

默认情况下,此 trait 只会将默认数据库连接包装在一个事务中。如果您的应用程序使用多个数据库连接,您应该在测试类中定义一个 $connectionsToTransact 属性。此属性应为一个连接名称数组,用于执行事务。

编写工厂

在测试时,您可能需要在执行测试之前向数据库插入一些记录。与其在创建这些测试数据时手动指定每个列的值,Laravel 允许您使用模型工厂为每个 Eloquent 模型 定义一组默认属性。要开始,请查看应用程序中的 database/factories/ModelFactory.php 文件。默认情况下,此文件包含一个工厂定义:

php
$factory->define(App\User::class, function (Faker\Generator $faker) {
    static $password;

    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => $password ?: $password = bcrypt('secret'),
        'remember_token' => str_random(10),
    ];
});

在作为工厂定义的闭包中,您可以返回模型上所有属性的默认测试值。闭包将接收 Faker PHP 库的一个实例,该库允许您方便地生成各种类型的随机数据进行测试。

当然,您可以自由地向 ModelFactory.php 文件添加自己的其他工厂。您还可以为每个模型创建额外的工厂文件以便更好地组织。例如,您可以在 database/factories 目录中创建 UserFactory.phpCommentFactory.php 文件。factories 目录中的所有文件将自动由 Laravel 加载。

工厂状态

状态允许您定义可以以任何组合应用于模型工厂的离散修改。例如,您的 User 模型可能有一个 delinquent 状态,该状态修改其默认属性值之一。您可以使用 state 方法定义状态转换:

php
$factory->state(App\User::class, 'delinquent', function ($faker) {
    return [
        'account_status' => 'delinquent',
    ];
});

使用工厂

创建模型

一旦定义了工厂,您可以在测试或种子文件中使用全局 factory 函数生成模型实例。让我们来看一些创建模型的示例。首先,我们将使用 make 方法创建模型,但不将其保存到数据库:

php
public function testDatabase()
{
    $user = factory(App\User::class)->make();

    // 在测试中使用模型...
}

您还可以创建多个模型的集合或创建给定类型的模型:

php
// 创建三个 App\User 实例...
$users = factory(App\User::class, 3)->make();

应用状态

您还可以将任何 状态 应用于模型。如果您想将多个状态转换应用于模型,您应该指定要应用的每个状态的名称:

php
$users = factory(App\User::class, 5)->states('delinquent')->make();

$users = factory(App\User::class, 5)->states('premium', 'delinquent')->make();

覆盖属性

如果您想覆盖模型的一些默认值,可以将值数组传递给 make 方法。只有指定的值会被替换,而其余的值将保持为工厂指定的默认值:

php
$user = factory(App\User::class)->make([
    'name' => 'Abigail',
]);

持久化模型

create 方法不仅创建模型实例,还使用 Eloquent 的 save 方法将其保存到数据库:

php
public function testDatabase()
{
    // 创建一个 App\User 实例...
    $user = factory(App\User::class)->create();

    // 创建三个 App\User 实例...
    $users = factory(App\User::class, 3)->create();

    // 在测试中使用模型...
}

您可以通过将数组传递给 create 方法来覆盖模型上的属性:

php
$user = factory(App\User::class)->create([
    'name' => 'Abigail',
]);

关系

在此示例中,我们将为一些创建的模型附加一个关系。使用 create 方法创建多个模型时,将返回一个 Eloquent 集合实例,允许您使用集合提供的任何方便函数,例如 each

php
$users = factory(App\User::class, 3)
           ->create()
           ->each(function ($u) {
                $u->posts()->save(factory(App\Post::class)->make());
            });

关系和属性闭包

您还可以使用工厂定义中的闭包属性将关系附加到模型。例如,如果您想在创建 Post 时创建一个新的 User 实例,可以执行以下操作:

php
$factory->define(App\Post::class, function ($faker) {
    return [
        'title' => $faker->title,
        'content' => $faker->paragraph,
        'user_id' => function () {
            return factory(App\User::class)->create()->id;
        }
    ];
});

这些闭包还接收定义它们的工厂的评估属性数组:

php
$factory->define(App\Post::class, function ($faker) {
    return [
        'title' => $faker->title,
        'content' => $faker->paragraph,
        'user_id' => function () {
            return factory(App\User::class)->create()->id;
        },
        'user_type' => function (array $post) {
            return App\User::find($post['user_id'])->type;
        }
    ];
});

可用的断言

Laravel 为您的 PHPUnit 测试提供了几个数据库断言:

方法描述
$this->assertDatabaseHas($table, array $data);断言数据库中的表包含给定数据。
$this->assertDatabaseMissing($table, array $data);断言数据库中的表不包含给定数据。
$this->assertSoftDeleted($table, array $data);断言给定记录已被软删除。