Laravel多数据库测试:解决重复执行迁移导致的失败问题
问题3:为何会重复执行已完成的migrations并导致测试失败?
核心原因是多数据库环境下,Laravel默认仅追踪默认数据库(core_db)的迁移记录,而express_db、regio_db没有独立的migrations表,或者你的迁移文件未指定对应数据库连接,导致Laravel无法识别这些数据库的迁移已完成。另外,使用RefreshDatabase trait时,默认只会重置默认数据库,但其他数据库的迁移状态没被正确同步,每次测试都会重新执行所有迁移,进而触发重复列、重复表的报错。
问题1:如何确保仅在必要时执行migrations,正确维护测试数据库状态?
给每个数据库配置独立的迁移追踪:
在每个迁移文件中指定对应的数据库连接,比如针对express_db的迁移:class AddClientIdToOrdersTable extends Migration { protected $connection = 'express'; public function up() { Schema::connection('express')->table('orders', function (Blueprint $table) { $table->unsignedBigInteger('client_id'); }); } }运行迁移时指定对应数据库,让Laravel在每个库的
migrations表中记录执行状态:php artisan migrate --database=core php artisan migrate --database=express php artisan migrate --database=regio这样测试时Laravel能识别各数据库的迁移完成情况,不会重复执行。
自定义测试trait替代RefreshDatabase:
编写一个自定义trait,先检查所有数据库的迁移状态,仅执行未完成的迁移,之后用事务包裹测试操作:trait MaintainDatabaseState { public function setUp(): void { parent::setUp(); // 执行所有数据库的未完成迁移 foreach (['core', 'express', 'regio'] as $connection) { $this->artisan('migrate', [ '--database' => $connection, '--force' => true, ]); } // 给所有数据库开启事务 foreach (['core', 'express', 'regio'] as $connection) { DB::connection($connection)->beginTransaction(); } } public function tearDown(): void { // 回滚所有数据库的事务 foreach (['core', 'express', 'regio'] as $connection) { DB::connection($connection)->rollBack(); } parent::tearDown(); } }在测试类中使用这个trait,替代
RefreshDatabase,首次运行时执行未完成迁移,后续测试仅通过事务回滚数据,不再重复迁移。预迁移测试数据库:
测试前手动执行一次所有数据库的迁移,之后测试类仅使用DatabaseTransactionstrait,让测试操作在事务中运行,结束后回滚数据,避免每次重建数据库和迁移。
问题2:如何在schema已更新时阻止每次运行migrations?
复用预迁移的测试数据库:
首次测试前执行全量迁移:php artisan migrate --database=core --env=testing php artisan migrate --database=express --env=testing php artisan migrate --database=regio --env=testing之后测试仅用
DatabaseTransactionstrait,每次测试只回滚数据,不重新迁移。当schema更新时,手动重新执行上述迁移命令即可。使用数据库快照:
迁移完成后,给测试数据库创建快照(比如用MySQL的mysqldump),每次测试前加载快照,比重新迁移更快。schema更新时,重新生成快照即可。基于迁移文件哈希判断是否重迁:
在自定义trait中,计算所有迁移文件的哈希值,和测试数据库存储的哈希对比,仅当哈希变化时才重新执行迁移:// 自定义trait的setUp方法中添加逻辑 $migrationHash = collect(glob(database_path('migrations/*.php'))) ->map(fn($file) => hash_file('sha256', $file)) ->sort() ->implode(''); $storedHash = Cache::store('file')->get('migration_hash'); if ($migrationHash !== $storedHash) { // 重新执行所有数据库的迁移 foreach (['core', 'express', 'regio'] as $connection) { $this->artisan('migrate:fresh', [ '--database' => $connection, '--force' => true, ]); } Cache::store('file')->put('migration_hash', $migrationHash); }这样只有迁移文件修改时才会重迁,否则直接复用现有数据库结构。
内容的提问来源于stack exchange,提问作者spyro95

