数据库测试

简介

Laravel 提供了各种有用的工具,可以更轻松地测试数据库驱动的应用程序。 首先, 你可以使用 assertDatabaseHas 辅助函数,来断言数据库中是否存在与指定条件互相匹配的数据。 例如, 如果我们想要验证 users 表中是否存在 email 值为 sally@example.com 的数据,你可以执行以下操作:

  1. public function testDatabase()
  2. {
  3. // 调用应该程序...
  4. $this->assertDatabaseHas('users', [
  5. 'email' => 'sally@example.com',
  6. ]);
  7. }

你也可以使用 assertDatabaseMissing 辅助函数断言数据库中不存在给定的数据。

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

生成模型工厂

使用 make:factory Artisan command 命令可以创建一个模型工厂:

  1. php artisan make:factory PostFactory

新生成的工厂位置在 database/factories 目录下。

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

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

每次测试后重置数据库

在每次测试后重置数据库是很有用的,这样前一次测试的数据不会干扰后续测试。 RefreshDatabase trait 会采用最优化的方法来迁移测试数据库,这取决于你使用的是内存数据库还是传统数据库。 在测试类中引用这个 trait ,一切都将为你处理:

  1. <?php
  2. namespace Tests\Feature;
  3. use Illuminate\Foundation\Testing\RefreshDatabase;
  4. use Illuminate\Foundation\Testing\WithoutMiddleware;
  5. use Tests\TestCase;
  6. class ExampleTest extends TestCase
  7. {
  8. use RefreshDatabase;
  9. /**
  10. * 一个基本的功能测试示例。
  11. *
  12. * @return void
  13. */
  14. public function testBasicExample()
  15. {
  16. $response = $this->get('/');
  17. // ...
  18. }
  19. }

编写模型工厂

进行测试时,运行测试之前常常需要插入一些数据到数据库中。当你创建测试数据时,除了手动设置每个字段的值,Laravel 还可以使用 Eloquent模型 的工厂来设置每个属性的默认值。在开始之前, 你可以先看一下应用程 database/factories/UserFactory.php 的文件。 开箱即用,这个文件包含一个模型工厂定义:

  1. use Faker\Generator as Faker;
  2. use Illuminate\Support\Str;
  3. $factory->define(App\User::class, function (Faker $faker) {
  4. return [
  5. 'name' => $faker->name,
  6. 'email' => $faker->unique()->safeEmail,
  7. 'email_verified_at' => now(),
  8. 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
  9. 'remember_token' => Str::random(10),
  10. ];
  11. });

在这个模型工厂定义的闭包中,你可以返回模型上所有属性的默认测试值。闭包将接受 PHP 函数库 Faker 的一个实例,它允许你便捷的生成各种随机数据来进行测试。

你也可以为每个模型创建一个工厂文件以便实现更好的组织。 例如,你可以自database/factories 目录下创建 UserFactory.phpCommentFactory.php 文件。 Laravel 将自动加载 factories 目录下的所有文件。

技巧:你也可以在 config/app.php 配置文件中添加 faker_locale 选项来设置 Faker 的语言环境。

工厂扩展

如果扩展了模型,则可能还希望扩展其工厂,以便在测试和数据填充期间利用子模型的工厂属性。 为此,你可以调用工厂制造商的 raw 方法来从任何给定工厂获得原始属性数组:

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

工厂状态

工厂状态可以让你任意组合你的模型工厂,仅需要做出适当差异化的修改,就可以达到让模型拥有多种不同的状态。例如, 你的 User 模型中可以修改某个默认属性值来达到标识一种 delinquent 状态。你可以使用 state 方法来进行这种状态转换。对于简单的工厂状态,你可以传入要修改的属性数组:

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

如果你的工厂状态需要计算或者使用 $faker 实例。你可以使用闭包方法来实现状态属性的修改:

  1. $factory->state(App\User::class, 'address', function ($faker) {
  2. return [
  3. 'address' => $faker->address,
  4. ];
  5. });

工厂回调

工厂回调是使用 afterMakingafterCreating 方法注册的,并且允许你在创建模型之后执行其他任务。例如,可以使用回调将附加模型与创建的模型相关联:

  1. $factory->afterMaking(App\User::class, function ($user, $faker) {
  2. // ...
  3. });
  4. $factory->afterCreating(App\User::class, function ($user, $faker) {
  5. $user->accounts()->save(factory(App\Account::class)->make());
  6. });

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

  1. $factory->afterMakingState(App\User::class, 'delinquent', function ($user, $faker) {
  2. // ...
  3. });
  4. $factory->afterCreatingState(App\User::class, 'delinquent', function ($user, $faker) {
  5. // ...
  6. });

使用模型工厂

创建模型

模型工厂定义后,就可以在测试或者是数据库的填充文件中,通过全局的 factory 函数来生成模型实例。因此,先让我们来看看几个模型创建的例子。 首先,我们将使用 make 方法创建模型但不将他们保存至数据库:

  1. public function testDatabase()
  2. {
  3. $user = factory(App\User::class)->make();
  4. // Use model in tests...
  5. }

你也可以创建一个含有多个模型的集合,或创建一个指定类型的模型:

  1. // Create three App\User instances...
  2. $users = factory(App\User::class, 3)->make();

应用状态

你也可将任何 状态 应用于模型。若将多个状态转换应用于模型,你应当为每个状态指定名称:

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

覆盖属性

如果要覆盖模型的某些默认值,你可以将一组值传递给make方法。 这样的话只有指定的值才会被替换,而其余值仍为工厂指定的默认值:

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

技巧:使用工厂创建模型时,将自动禁用 批量赋值

持久化模型

create 方法创建模型实例的同时还调用了把记录写入数据库的 save 方法:

  1. public function testDatabase()
  2. {
  3. // 创建单个 App\User 实例...
  4. $user = factory(App\User::class)->create();
  5. // 创建 3 个 App\User 实例..
  6. $users = factory(App\User::class, 3)->create();
  7. // 在测试中使用模型...
  8. }

传入一个数组给 create 方法重写模型的属性:

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

模型关联

在这个例子中,我们将为模型创建关联。使用 create 方法创建多模型时,返回一个 Eloquent 实例集合,这样就可以在集合上使用 each 等便捷方法:

  1. $users = factory(App\User::class, 3)
  2. ->create()
  3. ->each(function ($user) {
  4. $user->posts()->save(factory(App\Post::class)->make());
  5. });

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

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

关联 & 属性闭包

模型工厂定义时,也可以使用闭包里的属性来给模型添加关联。 例如,创建 Post 实例时,同时创建 User 实例,可以这样做:

  1. $factory->define(App\Post::class, function ($faker) {
  2. return [
  3. 'title' => $faker->title,
  4. 'content' => $faker->paragraph,
  5. 'user_id' => factory(App\User::class),
  6. ];
  7. });

如果该关系取决于定义它的工厂,则可以提供一个闭包接收一个包含工厂属性的数组:

  1. $factory->define(App\Post::class, function ($faker) {
  2. return [
  3. 'title' => $faker->title,
  4. 'content' => $faker->paragraph,
  5. 'user_id' => factory(App\User::class),
  6. 'user_type' => function (array $post) {
  7. return App\User::find($post['user_id'])->type;
  8. },
  9. ];
  10. });

使用数据填充

如果你希望在测试期间使用 数据填充 填充数据库,则可以使用 seed 方法。默认情况下, seed 方法将返回 DatabaseSeeder, 它会结束其他的数据填充器。 或者,将指定的数据填充器的类名传递给 seed 方法:

  1. <?php
  2. namespace Tests\Feature;
  3. use Illuminate\Foundation\Testing\RefreshDatabase;
  4. use Illuminate\Foundation\Testing\WithoutMiddleware;
  5. use OrderStatusesTableSeeder;
  6. use Tests\TestCase;
  7. class ExampleTest extends TestCase
  8. {
  9. use RefreshDatabase;
  10. /**
  11. * 测试创建新订单。
  12. *
  13. * @return void
  14. */
  15. public function testCreatingANewOrder()
  16. {
  17. // 运行整个数据填充器...
  18. $this->seed();
  19. // 运行单个数据填充器...
  20. $this->seed(OrderStatusesTableSeeder::class);
  21. // ...
  22. }
  23. }

可用的断言方法

Laravel 为 PHPUnit 测试提供了多个数据库断言方法:

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

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

例如,如果你在测试中使用模型工厂,则可以将此模型传递给删除断言方法,以测试你的应用程序已从数据库中正确删除了记录:

  1. public function testDatabase()
  2. {
  3. $user = factory(App\User::class)->create();
  4. // Make call to application...
  5. $this->assertDeleted($user);
  6. }

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接 我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

Laravel China 社区:https://learnku.com/docs/laravel/7.x/database-testing/7508