LoginSignup
2
1

More than 1 year has passed since last update.

【Laravel】RefreshDatabaseでデータがロールバックされない問題を解決する

Last updated at Posted at 2021-10-15

問題

LaravelでMySQLのDBテストを書いていたら、RefreshDatabaseでロールバックされないデータが発生した。

原因

テストで使っていたシーダー内でtruncate()を実行していたため。

解決方法

truncate()を使用しない。

状況の再現

たとえばhogeシーダーでは、truncate()で最初にテーブルデータの全件削除を行って、つぎにデータを投入している。

HogeTableSeeder.php
<?php

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

class HogeSeeder extends Seeder
{
    public function run()
    {
       DB::table('hoge')->truncate();
       DB::table('hoge')->insert(['name'=>'事前投入ほげほげ']);
    }
}

下記のようなDBテストで、RefreshSeederの実行&シーダーでのデータの流し込みをしているとする。

HogeTest.php
<?php

namespace Tests\Unit;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

use App\Hoge;

class HogeTest extends TestCase
{
    use RefreshDatabase; // テストごとにデータをリフレッシュする

    public function setUp(): void
    {
        parent::setUp();
        $this->seed(); // テスト開始時にシーダを実行する
    }

    public function testHoge1()
    {
        // hogeテーブルにデータを追加する
        $hoge = new Hoge();
        $hoge->name = "テストほげほげ";
        $hoge->save();
        // このテスト内ではデータは存在するはず
        $this->assertDatabaseHas('hoge', ['name' => "テストほげほげ"]);
    }

    public function testHoge2()
    {
        // 別のテストではtestHoge1()で追加したデータは存在しないことを期待している
        $this->assertDatabaseMissing('hoge', ['name' => "テストほげほげ"]);
    }
}

シーダーで流し込んだデータは全てのテストで共通して存在しており、各テストで追加したデータはそのテスト内のみで完結するように期待している。
しかしこのテストを実行すると、1つめのテストで追加したデータが残ったままで、2つめのテストは失敗する

また、テスト終了後のテーブルの中身はシーディングデータ含めすべて残っている(ロールバックされていない)。

id name
1 事前投入ほげほげ
2 テストほげほげ
3 事前投入ほげほげ

RefreshDatabaseとtruncate()を同時につかってはいけない

DB::table()->truncate()はテーブルの全件削除のメソッドで、sqlのtruncateを実行する。
db:seed時に前回のシーディングデータを削除したいがために使用されていたようだった。

一方、RefreshDatabaseはmirate:refreshを実行し、トランザクションを貼って最後にロールバックするメソッドである。
これら2つが併用されるとどうなるかというと、下記の様になる。

--テスト開始--
1. migrate:refresh実行
2. トランザクッション開始
3. シーダー実行
4. truncateの実行(暗黙的なコミットがはしる
5. テストメソッドの処理
6. tearDown()が呼ばれる
7. ロールバックができない
--テスト終了--

sqlで書くとこんなかんじ。

BEGIN;
TRUNCATE `test`.`hoge`;
INSERT INTO `test`.`hoge` (`id`, `name`) VALUES ('1', '事前投入ほげほげ');
INSERT INTO `test`.`hoge` (`id`, `name`) VALUES ('2', 'テストほげほげ');
ROLLBACK;

実行するとわかるが、トランザクション内でtruncateを実行するとcommitが走るので、その後のinsertで追加したデータは残ったままである。

解決方法 truncateを使用しない

migrate:fresh --seedすればいいだけなので、そもそもシーダーでtruncate()を呼ぶのはナンセンスである。
これだけのことを探すのにすごい時間がかかった。
参考:https://dev.mysql.com/doc/refman/8.0/ja/implicit-commit.html

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1