RefreshDatabase 通过在每个测试方法前后自动开启和回滚数据库事务实现数据清理,依赖事务支持的驱动(如MySQL、PostgreSQL)及未显式提交/回滚;其生效需满足连接复用隔离、禁用非事务操作、正确配置autocommit等条件。

RefreshDatabase 是怎么触发事务回滚的
RefreshDatabase 并不是靠“每次测试后手动执行 ROLLBACK”来清理数据,而是利用 Laravel 测试启动时的数据库连接生命周期,在每个测试方法开始前开启一个事务,在测试结束后自动回滚——前提是数据库驱动支持事务(如 MySQL、PostgreSQL),且未显式调用 DB::commit() 或 DB::rollback()。
- 它通过
Illuminate\Foundation\Testing\DatabaseTransactionstrait 注入逻辑,底层调用DB::beginTransaction()和DB::rollback() - 仅对
TestCase中标记为@test或继承自Tests\TestCase的方法生效 - 如果测试中使用了
DB::connection()->transaction(...)嵌套事务,可能干扰外层回滚行为(MySQL 不支持真正的嵌套事务) - SQLite 内存数据库默认启用,但文件型 SQLite 需确保配置中
'database' => ':memory:',否则事务回滚无法覆盖磁盘状态
为什么有些测试里 RefreshDatabase 像没生效
常见现象是:上一个测试插入了 User::factory()->create(),下一个测试仍能查到该记录,或断言失败。这通常不是 RefreshDatabase 失效,而是事务隔离或连接复用问题。
- 多个测试共用同一个数据库连接实例(尤其在
static::$user这类静态属性中缓存了模型实例),导致事务外的查询绕过回滚范围 - 使用了
DB::unprepared('TRUNCATE ...')或原生INSERT INTO ... SELECT等非事务安全操作,会提前提交隐式事务 - 测试中调用了
Artisan::call('migrate:fresh')或其他命令,破坏了事务上下文 - MySQL 配置了
autocommit=1且未被 Laravel 正确覆盖(检查config/database.php中对应连接的'options'是否含PDO::ATTR_AUTOCOMMIT => false)
RefreshDatabase 和 DatabaseMigrations 的关键区别
两者都用于测试前重置数据库,但机制和适用场景完全不同:
-
RefreshDatabase:不执行迁移,只靠事务回滚,速度快(毫秒级),适合大多数单元/功能测试;但要求所有 DDL 操作(如建表)已在测试启动前完成(例如php artisan migrate在phpunit.xml的bootstrap中执行) -
DatabaseMigrations:每个测试前运行migrate:rollback+migrate,清空并重建全部表结构,兼容任何 DDL 变更,但慢(秒级),且无法保留种子数据(除非配合Seeder显式调用) - 若测试中需要验证迁移本身(比如字段类型变更是否影响查询),必须用
DatabaseMigrations;若只是验证业务逻辑,RefreshDatabase更可靠
use Illuminate\Foundation\Testing\RefreshDatabase;
class ExampleTest extends TestCase
{
use RefreshDatabase;
public function test_user_creation()
{
$user = User::factory()->create(['email' => 'test@example.com']);
// 此处查询会命中事务内数据
$this->assertDatabaseHas('users', ['email' => 'test@example.com']);
// 测试结束时,整个事务被 rollback,$user 不会真正写入磁盘
}
}
事务回滚本身不保证跨连接可见性,也不处理外键约束延迟检查、触发器、或存储过程中的副作用——这些边界情况容易被忽略,但一旦出现,就会让测试行为变得不可预测。









