【问题标题】:How to seed database migrations for laravel tests?如何为 laravel 测试播种数据库迁移?
【发布时间】:2017-07-10 01:28:32
【问题描述】:

Laravel 的 documentation 建议使用 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

然后它适用于第一个测试,但它无法通过后续测试。这是因为 trait 会回滚迁移,并且当它再次运行迁移时,它不会为数据库做种。如何通过迁移运行数据库种子?

【问题讨论】:

    标签: php laravel testing laravel-migrations laravel-seeding


    【解决方案1】:

    在 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);
        }
    

    这种方法的好处在于,单个测试不会在每次运行时都播种。只有种子必要的测试才能建立数据库。

    【讨论】:

    • 当我更新到 php 8.0.9 时,setUp() 函数停止工作,在 PDO 回滚中出现错误。 $seed 属性解决了我的错误。谢谢
    【解决方案2】:

    在 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

    【讨论】:

    • 正是我需要的。我认为我的谷歌搜索可能会将我带到旧版本。谢谢!
    【解决方案3】:

    您只需在 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() 每个班级只运行一次,而不是每个测试。
    • 有没有办法在课堂测试而不是每次测试之前做到这一点?我尝试了setUpBeforeClass(),但它是一个静态函数,由于静态特性,我无法播种和做我需要的一切......不需要完全重置数据库(这对单元测试不利)。
    • 另外,您可以在setUp()方法中调用$this-&gt;seed()
    【解决方案4】:

    如果您正在使用 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');
        }
    }
    

    【讨论】:

      【解决方案5】:

      我知道这个问题已经回答了好几次了,但是我没有看到这个特定的答案,所以我想我会把它扔进去。

      在 laravel 中有一段时间(至少从 v5.5 开始),TestCase 类中有一个方法专门用于调用数据库播种器:

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

      使用此方法,您只需调用$this-&gt;seed('MySeederName'); 即可触发播种机。

      因此,如果您希望此播种机在每次测试之前触发,您可以将以下 setUp 函数添加到您的测试类中:

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

      最终结果是一样的:

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

      Artisan::call('db:seed', ['--class' => 'MySeederName'])
      

      但语法更简洁一些(在我看来)。

      【讨论】:

      • 这是我见过最干净的,你还需要$this-&gt;seed('RolesTableSeeder')
      【解决方案6】:

      如果您更愿意绕过 Artisan 的原生 DatabaseMigrations 和播种器/迁移方法,这里有一个替代解决方案。您可以创建自己的特征来播种数据库:

      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);
          }
      
      }
      

      【讨论】:

        【解决方案7】:

        我花了一些时间才弄清楚这一点,所以I thought I'd share

        如果您查看DatabaseMigrations trait 的源代码,您会发现它有一个函数runDatabaseMigrations,该函数由setUp 调用,runs before every test 并注册一个回调以在拆卸时运行。

        您可以通过给该函数起别名来排序"extend" the 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('/');
        
                // ...
            }
        }
        

        【讨论】:

        • 这应该在测试文档中!播种可能是测试的一个非常重要的部分,我没有看到任何提及这一点。如果我错了,请纠正我。
        • 很好的答案。以下是任何好奇如何创建播种机的文档的快捷方式:laravel.com/docs/5.6/seeding
        • 我很欣赏这里的创意,但它最终使我的测试花费了太长时间。 (github.com/ghsukumar/SFDC_Best_Practices/wiki/… 很有趣。)我现在正致力于让我的功能测试为空,并在测试套件开始时只重新植入数据库一次。 (并使用 Sqlite 代替 MySql。)
        • @Jeff Pucker 我不得不使用shell_exec('php artisan db:seed');,你的行$this-&gt;artisan('db:seed'); 对我不适用。但这是很棒的解决方案
        • 这个很棒的方法允许我们通过在覆盖runDatabaseMigrations() 中使用简单的条件if (in_array($this-&gt;getName(), $this-&gt;testsUsingDatabase)) ... 在一个测试用例中选择需要数据库迁移和播种的测试。 (这里的类成员$this-&gt;testsUsingDatabase应该是开发者定义的测试名称数组)
        猜你喜欢
        • 2020-06-04
        • 2016-11-29
        • 2021-11-20
        • 1970-01-01
        • 1970-01-01
        • 2017-06-07
        • 1970-01-01
        • 2020-07-07
        • 2014-08-14
        相关资源
        最近更新 更多