🌳

【Laravel8・PHP8】PHPUnitのテストケースの中でseedingするとPDOException

2021/09/19に公開

背景

テストでマスターデータが必要になった。

テストケース中でシーディングするときに詰まったのでメモ。

環境

  • Laravel 8.40
  • PHP 8.0
  • MySQL 8.0

事象

テストケースの中でシーディング実行するとエラー発生

$ php artisan test ./tests/Unit/UserTest.php 

   FAIL  Tests\Feature\UserTest
  ⨯ seed

  ---

  • Tests\Feature\UserTest > seed
   PDOException 

  There is no active transaction

  at vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php:279
    275▕      */
    276▕     protected function performRollBack($toLevel)
    277▕     {
    278▕         if ($toLevel == 0) {
  ➜ 279▕             $this->getPdo()->rollBack();
    280▕         } elseif ($this->queryGrammar->supportsSavepoints()) {
    281▕             $this->getPdo()->exec(
    282▕                 $this->queryGrammar->compileSavepointRollBack('trans'.($toLevel + 1))
    283▕             );


  Tests:  1 failed
  Time:   0.39s

実際のコード

UsersTableSeeder
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('users')->truncate();

        DB::table('users')->insert([
            'id' => 1,
            'name' => 'test name 1',
            'email' => 'test1@test.com',
            'email_verified_at' => now(),
            'password' => \Hash::make('password'),
            'remember_token' => Str::random(10),
        ]);
    }
}
UserTest.php
<?php

namespace Tests\Unit;

use Database\Seeders\UsersTableSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class UserTest extends TestCase
{
    use RefreshDatabase;

    public function test_seed()
    {
        $this->seed(UsersTableSeeder::class);

        $this->assertDatabaseHas('users', [
            'id' => 1,
            'name' => 'test name 1',
            'email' => 'test1@test.com',
        ]);
    }
}

解決策

テストケース中で使っているTraitをRefreshDatabase から DatabaseMigrations に変える。

コード

UserTest.php
<?php

namespace Tests\Unit;

use Database\Seeders\UsersTableSeeder;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\TestCase;

class UserTest extends TestCase
{
    use DatabaseMigrations;

    public function test_seed()
    {
        $this->seed(UsersTableSeeder::class);

        $this->assertDatabaseHas('users', [
            'id' => 1,
            'name' => 'test name 1',
            'email' => 'test1@test.com',
        ]);
    }
}

結果

$ php artisan test ./tests/Unit/UserTest.php 

   PASS  Tests\Feature\UserTest
  ✓ seed

  Tests:  1 passed
  Time:   0.42s

調査 (WIP

検索してみるとLaravelやPHPのバージョン起因によるもの?

同様なエラーについて議論しているIssueを発見

https://github.com/laravel/framework/pull/35988

コメントにはこの一文が

Before PHP 8, while there's no active transaction PDO doesn't throw any error but this not the same in PHP 8.

つまり8系以前でも同様の事象は発生していたが、エラーにならなかっただけと考えられる。

その事象がなぜ起きるかについては調査中
MySQLのドキュメントにはTruncateは暗黙的にコミットされるようなのでこれが関係している?
Laravelのissueで修正については議論されている模様

補足

間違い等のご指摘歓迎いたします。

GitHubで編集を提案

Discussion