SQLMock 无法自动 mock Rollback() 是因为事务方法由 sql.DB/sql.Tx 直接处理,不经过 driver 接口;必须显式调用 mock.ExpectBegin() 和 mock.ExpectRollback() 才能验证,且需确保业务代码真实触发 Rollback() 调用。

SQLMock 为什么 mock 不到 Rollback() 调用?
因为 sqlmock 默认不拦截事务方法,Begin()、Commit()、Rollback() 这些调用根本不会走 mock 的 driver 接口,而是直接由 sql.DB 或 sql.Tx 处理。你看到测试里没报错,不代表它被 mock 了——它压根没进 SQLMock 的监控范围。
实操上必须手动注册事务行为:
- 调用
mock.ExpectBegin()显式声明“接下来会调用Begin()” - 再调用
mock.ExpectRollback()告诉 mock:“我预期这里会触发回滚” - 如果事务里执行了语句(比如
INSERT),还要用mock.ExpectExec(...).WillReturnResult(...)配合 - 最后别忘了在测试末尾检查:
assert.NoError(t, mock.ExpectationsWereMet()),否则即使没触发Rollback()也不会报错
Go 测试中如何触发真实回滚?
回滚不是靠 mock 决定的,而是靠你的业务代码是否真调用了 tx.Rollback()。SQLMock 只负责“验证这个调用是否发生”,不负责“让它发生”。所以重点在业务逻辑分支设计:
- 让事务函数接收一个可注入的 error(比如
errToReturn error),并在关键位置返回它,触发 defer 里的tx.Rollback() - 避免在 defer 中无条件
Rollback()—— 这会导致每次测试都回滚,无法测成功路径;改用if tx != nil { tx.Rollback() }+ 显式置空 - 注意 Go 的 defer 执行顺序:多个 defer 按后进先出,
Rollback()必须在Commit()之后 defer,且用if err != nil控制 - 示例片段:
tx, _ := db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() if err := doSomething(tx); err != nil { tx.Rollback() return err } return tx.Commit()——这种写法里,只有doSomething报错才会走到Rollback()
sqlmock 对 sql.Tx 的兼容性陷阱
SQLMock 的 ExpectBegin() 返回的是一个假的 *sql.Tx,但它不实现所有 sql.Tx 方法。如果你业务代码里调用了 tx.Stmt()、tx.Prepare() 或者 tx.QueryContext() 等非标准路径,会 panic:“method not implemented”。
立即学习“go语言免费学习笔记(深入)”;
- 优先用
tx.Exec()/tx.Query()/tx.QueryRow()—— 这些是 SQLMock 明确支持的 - 如果必须用
tx.Stmt(),得自己 mock 对应的sql.Stmt行为,非常麻烦;建议重构为直接用tx.Exec() - Go 1.19+ 的
QueryRowContext()默认不被 SQLMock 拦截,需显式调用mock.ExpectQuery(...).WithContext(...) - 事务嵌套(
Savepoint)完全不支持,SQLMock 无 savepoint 相关 API,遇到就得绕开或降级测试粒度
为什么 mock.ExpectRollback() 总是失败?
最常见原因是:事务对象没传对,或者 Rollback() 根本没被执行。SQLMock 不会自动关联 Begin() 和后续的 Rollback(),它只按调用顺序匹配 ExpectXXX。
- 确保
mock.ExpectBegin()在db.Begin()之前调用,且只调用一次 - 确保业务函数里实际调用了
tx.Rollback()(而不是db.Close()或漏写了) - 检查是否有 panic 捕获但没 re-panic,导致
Rollback()执行了,但后续流程中断,ExpectationsWereMet() 没跑完 - 不要在多个 goroutine 里并发操作同一个 mock 实例——SQLMock 不是线程安全的,Expect 会被打乱
- 调试技巧:在
Rollback()前加日志,确认它真进了;再看 mock.Err() 是否有未满足的 expectation
事务回滚测试真正难的不是写 mock,而是让错误路径和资源清理逻辑足够清晰、可预测。一旦业务代码把 rollback 埋在多层 defer 或 recover 里,mock 就容易失焦——这时候宁可拆细函数,也不硬扛复杂控制流。










