Buffalo框架测试不自动回滚事务,必须手动用db.Transaction()开启事务并defer tx.Rollback()确保panic时也执行;所有操作须传入tx,避免隐式调用全局db,且SQLite内存库不等于事务回滚。

Buffalo 框架默认不支持测试后自动回滚事务
Buffalo 的 buffalo test 命令启动的是普通测试环境,数据库连接走的是标准 db.Connect(),不会开启事务包装,更不会在每个 TestXxx 函数结束后自动 ROLLBACK。你看到的“测试干净”往往只是因为用了内存 SQLite(如 sqlite3://:memory:),它天然隔离,不是事务回滚的结果。
用 tx.Rollback() 手动包裹测试逻辑是唯一可靠方式
Buffalo 本身没提供类似 Rails 的 use_transactional_tests 魔法开关。必须显式控制事务生命周期,核心是:在每个测试开始时开启事务,在结束前强制回滚 —— 即使测试 panic 也得保证回滚发生。
- 务必用
defer tx.Rollback(),不能只写tx.Rollback()在末尾,否则失败或 panic 时不会执行 - 事务对象
tx必须来自pop.Connection.Transaction(),不能直接对全局db调用Begin() - 所有数据库操作(
Create、Find、Destroy等)都要传入该tx,否则操作仍在主连接上,不受事务控制 - 示例片段:
func TestUserCreate(t *testing.T) {
app := buffalo.New(buffalo.Options{})
db := app.DB
tx, err := db.Transaction()
if !assert.NoError(t, err) {
return
}
defer tx.Rollback() // 关键:panic 时也会触发
u := &models.User{Name: "test"}
assert.NoError(t, tx.Create(u))
assert.NotZero(t, u.ID)
// 此处查询必须用 tx,而非 db
var found models.User
assert.NoError(t, tx.Find(&found, u.ID))
}
别依赖 sqlite3://:memory: 模拟回滚行为
很多 Buffalo 新手把测试数据库配成 sqlite3://:memory:,误以为这是“自动清理”,其实它只是每次连接新建一个空内存库。这在单测试中看似有效,但一旦启用并行测试(t.Parallel()),多个 goroutine 共享同一 :memory: 实例会导致数据污染和随机失败 —— 这不是回滚,是竞态。
Buffalo框架 1.0.1 版本源码包下载,适合需要错误处理改进、依赖更新、render.Download 注释和 request logger 调整的 v1 项目。
- 真实回滚必须基于支持事务的数据库(PostgreSQL / MySQL),且明确使用
Transaction() - 若坚持用 SQLite,至少改用带文件路径的临时 DB:
sqlite3:///tmp/test-xxx.db,并在defer os.Remove()清理 - PostgreSQL 测试推荐用
pgx驱动 +test_helper中预置的TruncateAll()辅助函数,比事务更稳定(尤其涉及外键或序列时)
复杂关联场景下,Transaction() 可能不生效
当模型含 has_many、belongs_to 或自定义钩子(BeforeCreate 等),且这些钩子内部又调用了 app.DB 而非传入的 tx,事务就会漏掉那部分操作 —— 数据仍会写入真实数据库。
- 检查所有钩子函数签名,确保它们接收并使用
tx pop.Connection参数 - 避免在钩子里硬编码
app.DB.Create(...),应改为tx.Create(...) - 若用
pop.Pop的关联保存(如tx.Eager().Load(&u, "Posts")),确认 eager 加载也运行在tx上,否则预加载查的是主库快照
事务回滚不是银弹;真正难缠的永远是那些悄悄绕过事务上下文的隐式 DB 调用,它们藏在钩子、中间件或第三方库封装里,得靠日志或断点逐层确认执行路径。

















