数据库测试
介绍
Laravel 提供了多种有用的工具,使测试数据库驱动的应用程序变得更容易。首先,您可以使用 assertDatabaseHas
辅助函数来断言数据库中存在与给定条件匹配的数据。例如,如果您想验证 users
表中是否存在 email
值为 sally@example.com
的记录,可以执行以下操作:
public function testDatabase()
{
// 调用应用程序...
$this->assertDatabaseHas('users', [
'email' => 'sally@example.com',
]);
}
您还可以使用 assertDatabaseMissing
辅助函数来断言数据库中不存在数据。
assertDatabaseHas
方法和其他类似的辅助函数是为了方便。您可以自由使用 PHPUnit 的任何内置断言方法来补充您的功能测试。
生成工厂
要创建工厂,请使用 make:factory
Artisan 命令:
php artisan make:factory PostFactory
新工厂将被放置在您的 database/factories
目录中。
--model
选项可用于指示工厂创建的模型的名称。此选项将使用给定的模型预填充生成的工厂文件:
php artisan make:factory PostFactory --model=Post
在每个测试后重置数据库
在每个测试后重置数据库通常是有用的,以便前一个测试的数据不会干扰后续测试。RefreshDatabase
trait 根据您使用的是内存数据库还是传统数据库,采用最优的方法迁移您的测试数据库。在您的测试类中使用该 trait,一切都会为您处理:
<?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
文件。默认情况下,此文件包含一个工厂定义:
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.php
和 CommentFactory.php
文件。factories
目录中的所有文件将自动由 Laravel 加载。
您可以通过在 config/app.php
配置文件中添加 faker_locale
选项来设置 Faker 的区域设置。
扩展工厂
如果您扩展了一个模型,您可能希望也扩展其工厂,以便在测试和播种期间利用子模型的工厂属性。为此,您可以调用工厂构建器的 raw
方法,从任何给定的工厂中获取原始属性数组:
$factory->define(App\Admin::class, function (Faker\Generator $faker) {
return factory(App\User::class)->raw([
// ...
]);
});
工厂状态
状态允许您定义可以以任何组合应用于模型工厂的离散修改。例如,您的 User
模型可能有一个 delinquent
状态,该状态修改其默认属性值之一。您可以使用 state
方法定义状态转换。对于简单的状态,您可以传递一个属性修改数组:
$factory->state(App\User::class, 'delinquent', [
'account_status' => 'delinquent',
]);
如果您的状态需要计算或 $faker
实例,您可以使用闭包来计算状态的属性修改:
$factory->state(App\User::class, 'address', function ($faker) {
return [
'address' => $faker->address,
];
});
工厂回调
工厂回调使用 afterMaking
和 afterCreating
方法注册,允许您在制作或创建模型后执行其他任务。例如,您可以使用回调将其他模型关联到创建的模型:
$factory->afterMaking(App\User::class, function ($user, $faker) {
// ...
});
$factory->afterCreating(App\User::class, function ($user, $faker) {
$user->accounts()->save(factory(App\Account::class)->make());
});
您还可以为 工厂状态 定义回调:
$factory->afterMakingState(App\User::class, 'delinquent', function ($user, $faker) {
// ...
});
$factory->afterCreatingState(App\User::class, 'delinquent', function ($user, $faker) {
// ...
});
使用工厂
创建模型
一旦您定义了工厂,您可以在功能测试或种子文件中使用全局 factory
函数生成模型实例。让我们来看一些创建模型的示例。首先,我们将使用 make
方法创建模型,但不将其保存到数据库:
public function testDatabase()
{
$user = factory(App\User::class)->make();
// 在测试中使用模型...
}
您还可以创建多个模型的集合或创建给定类型的模型:
// 创建三个 App\User 实例...
$users = factory(App\User::class, 3)->make();
应用状态
您还可以将任何 状态 应用于模型。如果您想将多个状态转换应用于模型,您应该指定要应用的每个状态的名称:
$users = factory(App\User::class, 5)->states('delinquent')->make();
$users = factory(App\User::class, 5)->states('premium', 'delinquent')->make();
覆盖属性
如果您想覆盖模型的一些默认值,可以将一个值数组传递给 make
方法。只有指定的值会被替换,而其余的值将保持为工厂指定的默认值:
$user = factory(App\User::class)->make([
'name' => 'Abigail',
]);
使用工厂创建模型时,批量赋值保护 会自动禁用。
持久化模型
create
方法不仅创建模型实例,还使用 Eloquent 的 save
方法将其保存到数据库:
public function testDatabase()
{
// 创建一个 App\User 实例...
$user = factory(App\User::class)->create();
// 创建三个 App\User 实例...
$users = factory(App\User::class, 3)->create();
// 在测试中使用模型...
}
您可以通过传递一个数组给 create
方法来覆盖模型上的属性:
$user = factory(App\User::class)->create([
'name' => 'Abigail',
]);
关系
在此示例中,我们将为一些创建的模型附加一个关系。使用 create
方法创建多个模型时,将返回一个 Eloquent 集合实例,允许您使用集合提供的任何便捷函数,例如 each
:
$users = factory(App\User::class, 3)
->create()
->each(function ($user) {
$user->posts()->save(factory(App\Post::class)->make());
});
您可以使用 createMany
方法创建多个相关模型:
$user->posts()->createMany(
factory(App\Post::class, 3)->make()->toArray()
);
关系和属性闭包
您还可以在工厂定义中将关系附加到模型。例如,如果您想在创建 Post
时创建一个新的 User
实例,可以执行以下操作:
$factory->define(App\Post::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => factory(App\User::class),
];
});
如果关系依赖于定义它的工厂,您可以提供一个接受评估属性数组的回调:
$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
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); | 断言给定记录已被软删除。 |
为了方便起见,您可以将模型传递给 assertDeleted
和 assertSoftDeleted
辅助函数,以根据模型的主键断言记录已从数据库中删除或软删除。
例如,如果您在测试中使用模型工厂,可以将此模型传递给这些辅助函数之一,以测试您的应用程序是否正确删除了数据库中的记录:
public function testDatabase()
{
$user = factory(App\User::class)->create();
// 调用应用程序...
$this->assertDeleted($user);
}