数据库测试
介绍
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 Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
class ExampleTest extends TestCase
{
use RefreshDatabase;
/**
* 一个基本的功能测试示例。
*
* @return void
*/
public function testBasicExample()
{
$response = $this->get('/');
// ...
}
}
编写工厂
在测试时,您可能需要在执行测试之前向数据库插入一些记录。与其在创建这些测试数据时手动指定每个列的值,Laravel 允许您使用模型工厂为每个 Eloquent 模型 定义一组默认属性。要开始,请查看应用程序中的 database/factories/UserFactory.php
文件。默认情况下,此文件包含一个工厂定义:
use Illuminate\Support\Str;
use Faker\Generator as Faker;
$factory->define(App\User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
'remember_token' => Str::random(10),
];
});
在作为工厂定义的闭包中,您可以返回模型上所有属性的默认测试值。闭包将接收 Faker PHP 库的一个实例,该库允许您方便地生成各种类型的随机数据以进行测试。
您还可以为每个模型创建额外的工厂文件以便更好地组织。例如,您可以在 database/factories
目录中创建 UserFactory.php
和 CommentFactory.php
文件。factories
目录中的所有文件将自动由 Laravel 加载。
您可以通过在 config/app.php
配置文件中添加 faker_locale
选项来设置 Faker 的区域设置。
工厂状态
状态允许您定义可以以任何组合应用于模型工厂的离散修改。例如,您的 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' => function () {
return factory(App\User::class)->create()->id;
}
];
});
这些闭包还接收定义它们的工厂的已评估属性数组:
$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;
}
];
});
使用种子
如果您想在测试期间使用 数据库种子 填充数据库,可以使用 seed
方法。默认情况下,seed
方法将返回 DatabaseSeeder
,它应该执行所有其他种子。或者,您可以将特定的种子类名称传递给 seed
方法:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use OrderStatusesTableSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
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->assertSoftDeleted($table, array $data); | 断言给定记录已被软删除。 |