Skip to content

数据库测试

介绍

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

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

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

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

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

生成工厂

要创建工厂,请使用 make:factory Artisan 命令

php
php artisan make:factory PostFactory

新工厂将被放置在您的 database/factories 目录中。

--model 选项可用于指示工厂创建的模型的名称。此选项将使用给定的模型预填充生成的工厂文件:

php
php artisan make:factory PostFactory --model=Post

在每个测试后重置数据库

在每个测试后重置数据库通常是有用的,以便前一个测试的数据不会干扰后续测试。RefreshDatabase trait 根据您使用的是内存数据库还是传统数据库,采用最优的方法迁移您的测试数据库。在您的测试类中使用该 trait,一切都会为您处理:

php
<?php

namespace Tests\Feature;

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

class ExampleTest extends TestCase
{
    use RefreshDatabase;

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

        // ...
    }
}

编写工厂

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

php
use Faker\Generator as Faker;
use Illuminate\Support\Str;

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // 密码
        'remember_token' => Str::random(10),
    ];
});

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

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

lightbulb

您可以通过在 config/app.php 配置文件中添加 faker_locale 选项来设置 Faker 的区域设置。

扩展工厂

如果您扩展了一个模型,您可能希望也扩展其工厂,以便在测试和播种期间利用子模型的工厂属性。为此,您可以调用工厂构建器的 raw 方法,从任何给定的工厂中获取原始属性数组:

php
$factory->define(App\Admin::class, function (Faker\Generator $faker) {
    return factory(App\User::class)->raw([
        // ...
    ]);
});

工厂状态

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

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

如果您的状态需要计算或 $faker 实例,您可以使用闭包来计算状态的属性修改:

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

工厂回调

工厂回调使用 afterMakingafterCreating 方法注册,允许您在制作或创建模型后执行其他任务。例如,您可以使用回调将其他模型关联到创建的模型:

php
$factory->afterMaking(App\User::class, function ($user, $faker) {
    // ...
});

$factory->afterCreating(App\User::class, function ($user, $faker) {
    $user->accounts()->save(factory(App\Account::class)->make());
});

您还可以为 工厂状态 定义回调:

php
$factory->afterMakingState(App\User::class, 'delinquent', function ($user, $faker) {
    // ...
});

$factory->afterCreatingState(App\User::class, 'delinquent', function ($user, $faker) {
    // ...
});

使用工厂

创建模型

一旦您定义了工厂,您可以在功能测试或种子文件中使用全局 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',
]);
lightbulb

使用工厂创建模型时,批量赋值保护 会自动禁用。

持久化模型

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 ($user) {
                $user->posts()->save(factory(App\Post::class)->make());
            });

您可以使用 createMany 方法创建多个相关模型:

php
$user->posts()->createMany(
    factory(App\Post::class, 3)->make()->toArray()
);

关系和属性闭包

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

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

如果关系依赖于定义它的工厂,您可以提供一个接受评估属性数组的回调:

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

使用种子

如果您想在功能测试期间使用 数据库播种器 填充数据库,可以使用 seed 方法。默认情况下,seed 方法将返回 DatabaseSeeder,它应该执行所有其他播种器。或者,您可以将特定的播种器类名传递给 seed 方法:

php
<?php

namespace Tests\Feature;

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

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * 测试创建新订单。
     *
     * @return void
     */
    public function testCreatingANewOrder()
    {
        // 运行 DatabaseSeeder...
        $this->seed();

        // 运行单个播种器...
        $this->seed(OrderStatusesTableSeeder::class);

        // ...
    }
}

可用的断言

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

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

为了方便起见,您可以将模型传递给 assertDeletedassertSoftDeleted 辅助函数,以根据模型的主键断言记录已从数据库中删除或软删除。

例如,如果您在测试中使用模型工厂,可以将此模型传递给这些辅助函数之一,以测试您的应用程序是否正确删除了数据库中的记录:

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

    // 调用应用程序...

    $this->assertDeleted($user);
}