数据库测试
简介
Laravel 为测试使用数据库的应用提供了各种有用的工具。首先,可以使用 assertDatabaseHas
辅助方法断言数据库中是否存在与给定的数组数据相匹配的数据。例如,如果要验证 users
数据表中是否存在 email
值为 sally@example.com
的记录,可以这样做:
public function testDatabase()
{
// 调用应用
$this->assertDatabaseHas('users', [
'email' => 'sally@example.com'
]);
}
也可以使用 assertDatabaseMissing
辅助方法断言数据在数据库中不存在。
当然,assertDatabaseHas
方法和其它类似的辅助方法都是为了方便。您可以自由使用任何 PHPUnit 自带的断言方法补充测试。
生成工厂
要创建工厂,可以使用 Artisan 命令 make:factory
:
php artisan make:factory PostFactory
新的工厂会放在 database/factories
目录中。
--model
选项可用于指定工厂所创建的模型的名称。此选项会使用给定模型预先填充生成的工厂文件:
php artisan make:factory PostFactory --model=Post
每次测试后重置数据库
在每次测试后重置数据库通常很有用,这样之前的测试就不会影响后续的测试。RefreshDatabase
Trait 会根据使用的是内存数据库该是传统的数据库,使用最佳方法迁移测试数据库。在测试类中使用此 Trait,一切都将为您处理:
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 Faker\Generator as Faker;
$factory->define(App\User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
'remember_token' => str_random(10),
];
});
在定义工厂的闭包中,可以返回模型上所有属性的默认测试值。闭包接收一个 Faker PHP 库的实例,您可以方便地生成各种类型的随机测试数据。
为了更好地组织代码,还可以为每个模型都创建一个工厂文件。例如,可以在 database/factories
目录中创建 UserFactory.php
和 CommentFactory.php
文件。Laravel 会自动加载 factories
目录中的所有文件。
可以通过在
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();
// 在测试中使用模型
}
也可以创建包含很多模型的集合或者创建给定类型的模型:
// Create three App\User instances...
$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 ($u) {
$u->posts()->save(factory(App\Post::class)->make());
});
关联 & 属性闭包
也可以在定义工厂时使用闭包属性为模型添加关联。例如,如果要在创建 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;
}
];
});
可用的断言
Laravel 为 PHPUnit 测试提供几个数据库断言:
方法 | 描述 |
---|---|
$this->assertDatabaseHas($table, array $data); |
断言数据库的数据表中包含给定数据 |
$this->assertDatabaseMissing($table, array $data); |
断言数据库的数据表中不包含给定数据 |
$this->assertSoftDeleted($table, array $data); |
断言给定记录已被软删除 |