refreshdatabase 不会建表,仅通过事务回滚清理数据;若报“base table not found”,说明表结构未在测试前手动迁移完成,需提前执行 php artisan migrate。

RefreshDatabase 不会在测试中自动执行迁移 —— 它默认跳过迁移,只靠事务回滚清理数据。如果你看到“表不存在”或“列不存在”错误,说明你误以为它会建表,其实它要求所有表结构必须已在测试启动前就绪。
RefreshDatabase 为什么没建表,却报错“Base table not found”
RefreshDatabase 的核心机制是事务回滚(DB::beginTransaction() + DB::rollback()),不是重建数据库。它不调用 migrate 或 migrate:fresh,因此:
- ✅ 前提:所有迁移必须在测试运行前手动执行(例如在
phpunit.xml的bootstrap中跑php artisan migrate) - ❌ 如果没提前迁移,
RefreshDatabase就会在空库上开启事务,一查表就抛出SQLSTATE[42S02]: Base table or view not found - ⚠️ SQLite 文件型配置(如
'database' => database_path('database.sqlite'))会导致回滚失效,务必改用':memory:'才能保证事务行为一致
RefreshDatabase 和 DatabaseMigrations 到底该选哪个
选哪个取决于你是否需要验证 DDL 变更(比如新增字段、修改索引):
-
用
RefreshDatabase:- 测试纯业务逻辑(CRUD、状态流转、命令执行)
- 要求快(毫秒级重置)
- 已确认所有表结构稳定且提前迁移完成
-
用
DatabaseMigrations:- 测试本身涉及迁移逻辑(例如验证
Schema::table(...)->dropColumn()是否生效) - 团队频繁改表结构,无法保证“提前迁移”的一致性
- 不介意每次测试慢 1–3 秒(因要
migrate:rollback+migrate)
- 测试本身涉及迁移逻辑(例如验证
两者不能混用;同时 use 会导致行为不可预测,DatabaseMigrations 会覆盖 RefreshDatabase 的事务逻辑。
RefreshDatabase 在 MySQL 上回滚失败的常见原因
事务回滚看似简单,但在 Laravel 测试中容易被几个底层细节破坏:
- MySQL 连接启用了
autocommit=1:检查config/database.php对应连接的'options',必须包含PDO::ATTR_AUTOCOMMIT => false - 测试中执行了非事务安全操作:
-
DB::unprepared('TRUNCATE users')(TRUNCATE 会隐式提交) -
DB::statement('CREATE TABLE ...')(DDL 强制提交) -
Artisan::call('migrate:fresh')(彻底破坏事务上下文)
-
- 模型实例被静态缓存:比如
static $user存了User::find(1),后续调用$user->email读的是旧内存副本,而非回滚后数据库的真实状态
事务回滚是 RefreshDatabase 的根基,但它完全依赖数据库驱动行为和 PHP 层连接配置的一致性。一旦某个测试里出现“上一个测试的数据还在”,不要急着换 Trait,先查连接复用、autocommit 设置、有没有 TRUNCATE 或 DDL —— 这些地方出问题的概率远高于 Trait 本身失效。











