数据库事务的测试,在Go里一直是个容易踩坑的环节。特别是用SQLMock这类工具时,很多细节一旦没注意,测试跑得再绿,也拦不住上线后出问题。这里几个关键点,值得花点时间捋清楚。

sqlmock.ExpectBegin() 必须在 mock.ExpectQuery() 之前调用

SQLMock 不会自动帮你拦截 db.Begin() 调用,这是个关键点。你以为测试没 panic 就万事大吉?其实事务根本没进入 SQLMock 的监控范围。必须显式地告诉它“接下来要 Begin”,否则后续对 *sql.Tx 的任何操作——比如 tx.QueryRow()——都会直接触发 no query expectations were met 错误。

常见的错误写法是先写 mock.ExpectQuery("SELECT..."),再调用 db.Begin()。正确的顺序应该是:

顺便提一句:ExpectQuery() 默认匹配的是 *sql.DB 上的调用。当绑定到事务时,SQLMock 会自动识别上下文,但前提是 ExpectBegin() 已经触发。

回滚测试必须显式 ExpectRollback(),且不能靠 defer 隐式触发

tx.Rollback() 本质上是一个普通的方法调用,SQLMock 默认不会拦截它。如果你不写 mock.ExpectRollback(),哪怕业务代码确实调用了 tx.Rollback(),测试也不会进行校验——更糟糕的是,ExpectationsWereMet() 还会通过,造成“假成功”的错觉。

实际操作中需要注意:

这里有个典型的陷阱:写了 defer tx.Rollback() 后又 return tx.Commit(),结果 Rollback() 根本没有执行。但测试因为漏写了 ExpectRollback(),也不会报错,问题就这么被掩盖过去了。

事务内 QueryRowContext() 需要 WithContext() 显式声明

在 Go 1.19+ 中,QueryRowContext() 默认不被 SQLMock 拦截。即使你写了 mock.ExpectQuery("SELECT..."),也会直接 panic,报 “no query expectations were met”。这并非 bug,而是 SQLMock 的设计限制。

解决办法只有一种:

别想着绕过去:用 QueryRow() 替代,或者删除 context 参数——这样做会让测试和生产行为不一致,反而掩盖了真实问题。

列名、类型、顺序三者必须和 Scan 或 StructScan 严格对齐

使用 sqlx.StructScan 时,如果失败或字段为空,90% 的可能性是列定义不匹配。SQLMock 不解析 SQL,它只按照你声明的列名去构造返回行,而 StructScan 又严格依赖 struct tag(比如 db:"created_at")和 SQL 返回列名小写完全一致。

需要重点检查的地方:

最容易忽略的细节是:SQL 中使用了别名,但没有同步更新 NewRows 的声明;或者 struct tag 混用了 json:"id"db:"name",导致部分字段映射失败。这些看似不起眼的问题,往往会在关键时刻给你致命一击。

本文转载于:https://www.php.cn/faq/2344910.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。