SQLMock 不拦截 Rollback(),ExpectRollback() 仅验证调用是否发生;它必须在 ExpectBegin() 后、业务执行前声明,并依赖业务代码在 err != nil 时显式调用 tx.Rollback() 才能生效。

SQLMock 本身不拦截 Rollback() 调用,你写 mock.ExpectRollback() 不是为了“让它回滚”,而是为了“验证它被调用了”。业务代码是否真执行 tx.Rollback(),完全取决于你如何设计错误分支——mock 只负责盯住那一次调用。
为什么 Rollback() 总是 mock 不到?
因为 sql.Tx.Rollback() 是标准库直接实现的方法,不经过 driver 接口。SQLMock 的拦截点在 SQL 执行层(Exec、Query),而事务控制方法(Begin、Commit、Rollback)压根不会走到 mock 的逻辑里。
常见错误现象:test passes but Rollback() never triggered —— 表面没报错,其实根本没进期望校验流程。
-
mock.ExpectRollback()必须显式声明,且必须在mock.ExpectBegin()之后、业务函数执行前设置 - 如果业务代码里
defer tx.Rollback()无条件执行,那ExpectCommit()就永远过不了;得改成if err != nil { tx.Rollback() } - 别漏掉
mock.ExpectationsWereMet()—— 没这句,哪怕Rollback()根本没发生,测试也 green
mock.ExpectRollback() 怎么配才有效?
它不是独立生效的,必须和事务生命周期对齐:开始 → 执行语句 → 出错 → 回滚。任意一环缺失,期望就断了。
立即学习“go语言免费学习笔记(深入)”;
典型配置顺序:
mock.ExpectBegin()-
mock.ExpectExec("UPDATE ...").WillReturnResult(...)(成功语句) -
mock.ExpectExec("INSERT ...").WillReturnError(fmt.Errorf("boom"))(故意让第二条失败) -
mock.ExpectRollback()(告诉 mock:“接下来这里必须回滚”)
注意:WillReturnError() 触发的是业务层 error 返回,从而走到你代码里的 if err != nil { tx.Rollback() } 分支——这才是 ExpectRollback() 能捕获的前提。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
业务代码里怎么写才能触发真实回滚?
关键不在 mock,而在 defer 和 error 处理的组合方式。常见陷阱是 defer 里无脑 tx.Rollback(),导致 Commit 永远不执行。
推荐写法(精简版):
func doInTx(db *sql.DB, fn func(*sql.Tx) error) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := fn(tx); err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
要点:
- 把事务控制逻辑收拢到一个可测的 wrapper 函数里,避免每个 DAO 方法都手写 begin/commit/rollback
- 不要在 defer 中直接调用
tx.Rollback(),除非你同时处理 panic;否则只在err != nil分支里调 - 确保
fn(tx)内部某处会返回非 nil error(比如注入errToReturn参数),否则 rollback 永远不会发生
tx.Stmt() 或 tx.QueryContext() 导致 panic 怎么办?
SQLMock 默认只实现了 tx.Query()、tx.QueryRow()、tx.Exec()。一旦业务代码调用 tx.Stmt() 或 tx.QueryContext(),就会 panic:method not implemented。
这不是配置问题,是 SQLMock 的能力边界:
-
tx.Stmt()需要额外 mocksql.Stmt行为,非常繁琐;建议重构为直接用tx.Exec()或tx.Query() -
tx.QueryContext()在较新版本 SQLMock 中已支持,但需确认你用的是 v1.5.0+;旧版本只能降级用tx.Query() - 如果你用了 GORM,它内部大量使用
Stmt和上下文方法,SQLMock 基本不可用——这时应考虑用gorm/gormtest或内存 SQLite
最常被忽略的一点:事务测试不是“让回滚发生”,而是“证明回滚被正确调用”。所有 mock 配置、defer 控制、error 注入,最终都服务于这一件事——别让它变成掩盖逻辑缺陷的装饰品。

















