如何为 Laravel 测试准备数据库迁移?

47

Laravel的文档建议使用DatabaseMigrations trait 在测试之间迁移和回滚数据库。

use Illuminate\Foundation\Testing\DatabaseMigrations;

class ExampleTest extends TestCase
{
    use DatabaseMigrations;

    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $response = $this->get('/');

        // ...
    }
}

然而, 我有一些种子数据,我希望在我的测试中使用。如果我运行:

php artisan migrate --seed

它可以在第一次测试时正常工作,但在后续测试中失败。这是因为特性会回滚迁移,并且当它再次运行迁移时,不会对数据库进行种子填充。如何在迁移期间运行数据库种子填充?


你可以运行此命令->seed() 来运行所有的数据填充器,或者使用 $this->seed(YourClass::class) 命令来运行特定的数据填充器。更多详情请参考 Laravel 文档: https://laravel.com/docs/7.x/database-testing#using-seeds - Tuncay Elvanağaç
7个回答

46

你需要做的就是在setUp函数中进行一个工匠调用db:seed

<?php

use Illuminate\Foundation\Testing\DatabaseMigrations;

class ExampleTest extends TestCase
{
    use DatabaseMigrations;

    public function setUp(): void
    {
        parent::setUp();

        // seed the database
        $this->artisan('db:seed');
        // alternatively you can call
        // $this->seed();
    }

    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $response = $this->get('/');

        // ...
    }
}

参考:https://laravel.com/docs/5.6/testing#creating-and-running-tests


对我有用。谢谢。之前我错误地认为setUp()只在每个类中运行一次而不是每个测试运行一次。 - Hyder B.
5
在课堂测试时,有没有办法不在每个测试之前都要重新设置?我尝试过setUpBeforeClass(),但它是一个静态函数,由于静态特性,我无法进行所需的种子和其他操作...在setUp()中进行操作会很慢,尤其是当你需要运行一堆不需要完全重置数据库的测试时(这对于单元测试来说并不好)。 - SteamFire
4
此外,在setUp()方法中,您可以调用$this->seed() - Erich

38

使用 Laravel 8,如果您正在使用 RefreshDatabase 特性,您可以在测试用例中使用以下代码来调用数据填充:

use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        // Run the DatabaseSeeder...
        $this->seed();

        // Run a specific seeder...
        $this->seed(OrderStatusSeeder::class);

        $response = $this->get('/');

        // ...
    }
}

查看文档以获取更多信息/示例: https://laravel.com/docs/8.x/database-testing#running-seeders


正是我所需要的。我想我的谷歌搜索可能会带我去旧版本。谢谢! - Nick
7
您可以使用 protected bool $seed = true; 在所有测试之前种子化一次。 - Tofandel

27

我费了一些心思才搞明白,所以我想分享一下。

如果你查看DatabaseMigrations trait的源代码,就会发现它只有一个函数runDatabaseMigrations,该函数由setUp调用,在每个测试之前运行并注册一个回调函数在撤销时运行。

你可以通过给这个函数取别名的方式“扩展”这个trait,重新声明一个新的函数并在原始名称下定义你的逻辑(artisan db:seed),然后在函数内调用这个别名。

use Illuminate\Foundation\Testing\DatabaseMigrations;

class ExampleTest extends TestCase
{
    use DatabaseMigrations {
        runDatabaseMigrations as baseRunDatabaseMigrations;
    }

    /**
     * Define hooks to migrate the database before and after each test.
     *
     * @return void
     */
    public function runDatabaseMigrations()
    {
        $this->baseRunDatabaseMigrations();
        $this->artisan('db:seed');
    }

    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $response = $this->get('/');

        // ...
    }
}

12
这应该被包含在测试文档中!填充数据(Seeding)可以是测试中非常重要的一部分,但我没有看到任何提及。如果我错了请纠正我。 - Adam Menczykowski
很棒的答案。这是一个快捷方式,供任何想知道如何创建种子文件的人查看文档:https://laravel.com/docs/5.6/seeding - JP Lew
1
我欣赏这里的创造力,但最终导致我的测试时间太长了。(https://github.com/ghsukumar/SFDC_Best_Practices/wiki/F.I.R.S.T-Principles-of-Unit-Testing 很有趣。)现在我正在努力使我的功能测试为空,并在测试套件开始时仅重新填充一次数据库。(并使用 Sqlite 而不是 MySql。) - Ryan
@Jeff Pucker 我不得不使用shell_exec('php artisan db:seed');,你的代码$this->artisan('db:seed');对我来说行不通。 但这是一个很棒的解决方案。 - FosAvance
这种优秀的方法允许我们在一个测试用例中选择需要数据库迁移和填充的测试,只需使用简单的条件 if (in_array($this->getName(), $this->testsUsingDatabase)) ... 覆盖 runDatabaseMigrations() 方法即可。 (这里类成员 $this->testsUsingDatabase 应该是开发人员定义的测试名称数组) - Prisacari Dmitrii

18

我知道这个问题已经被多次回答了,但是我没有看到这个特定的答案,所以我想加入一些内容。

在 Laravel 中有一个方法可以用来调用数据库填充类,在 TestCase 类中至少自 v5.5 以来已经存在:

https://laravel.com/api/5.7/Illuminate/Foundation/Testing/TestCase.html#method_seed

使用此方法,您只需调用$this->seed('MySeederName'); 即可启动填充类。

因此,如果您希望在每个测试之前启动填充类,可以将以下 setUp 函数添加到您的测试类中:

public function setUp()
{
    parent::setUp();
    $this->seed('MySeederName');
}

最终结果与以下相同:

 $this->artisan('db:seed',['--class' => 'MySeederName'])
或者
Artisan::call('db:seed', ['--class' => 'MySeederName'])

但是语法更加简洁(在我看来)。


这是我见过的最干净的,你还需要什么比$this->seed('RolesTableSeeder')更多的东西吗? - MeMReS

13

在 Laravel 8 中,RefreshDatabase 现在会寻找一个名为 "seed" 的布尔属性。

    /** 
     * Illuminate\Foundation\Testing\RefreshDatabase
     * Determine if the seed task should be run when refreshing the database.
     *
     * @return bool
     */
    protected function shouldSeed()
    {
        return property_exists($this, 'seed') ? $this->seed : false;
    }

如果你想要种子,请在测试类中设置受保护的属性$seed为true。

class ProjectControllerTest extends TestCase
{

    protected $seed = true;
    public function testCreateProject()
    {
        $project = Project::InRandomOrder()->first();
        $this->assertInstanceOf($project,Project::class);
    }

这种方法的好处在于,每次运行单个测试时不会重新生成种子。只有必要生成测试的种子才会构建数据库。


1
setUp() 函数在我升级到 php 8.0.9 后停止工作,PDO 回滚中出现错误。$seed 属性解决了我的错误。谢谢。 - Aitor

5
如果您正在使用RefreshDatabase测试特性:
abstract class TestCase extends BaseTestCase
{
    use CreatesApplication, RefreshDatabase {
        refreshDatabase as baseRefreshDatabase;
    }

    public function refreshDatabase()
    {
        $this->baseRefreshDatabase();

        // Seed the database on every database refresh.
        $this->artisan('db:seed');
    }
}

0

这里提供了另一种解决方案,以防您想要绕开Artisan的本地Database Migrations和seeder/migration方法。您可以创建自己的特性来填充您的数据库:

namespace App\Traits;

use App\Models\User;
use App\Models\UserType;

trait DatabaseSetup 
{

    public function seedDatabase()
    {
        $user = $this->createUser();
    }

    public function createUser()
    {
        return factory(User::class)->create([
            'user_type_id' => function () {
                return factory(UserType::class)->create()->id;
            }
        ]);
    }

    public function getVar() {
        return 'My Data';
    }
}

然后在你的测试中像这样调用它:
use App\Traits\DatabaseSetup;

class MyAwesomeTest extends TestCase
{
    use DatabaseSetup;
    use DatabaseTransactions;

    protected $reusableVar;

    public function setUp()
    {
        parent::setUp();
        $this->seedDatabase();
        $this->reusableVar = $this->getVar();
    }

    /**
     * @test
     */
    public function test_if_it_is_working()
    {
        $anotherUser = $this->createUser();
        $response = $this->get('/');
        $this->seeStatusCode(200);
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接