
本文详解 sqlmock 中 ExpectPrepare 的正确用法,指出早期版本需链式调用 ExpectQuery() 才能触发校验,以及该问题已在新版中修复,现可直接验证预处理语句是否匹配预期 SQL。
本文详解 sqlmock 中 `expectprepare` 的正确用法,指出早期版本需链式调用 `expectquery()` 才能触发校验,以及该问题已在新版中修复,现可直接验证预处理语句是否匹配预期 sql。
在 Go 的数据库单元测试中,sqlmock 是模拟 database/sql 行为的主流工具。但初学者常误以为调用 mock.ExpectPrepare("pattern") 后,只要代码中执行了 db.Prepare(),mock 就会自动校验 SQL 是否匹配——事实并非如此。
关键在于:ExpectPrepare() 返回的是一个 *sqlmock.ExpectedPrepare 对象,它本身不构成完整期望(expectation),而仅是一个“准备就绪”的声明。若未进一步指定该预处理语句后续如何被使用(例如被查询、执行或查询多行),sqlmock 将默认忽略该 Prepare 调用,导致测试“静默通过”,即使 SQL 完全错误。
✅ 正确做法(兼容旧版与明确语义):
必须链式调用 ExpectQuery()、ExpectExec() 或 ExpectQueryRow() 等方法,表明该预处理语句预期被用于对应操作:
// ✅ 正确:声明 Prepare + 明确预期其后执行 Query()
mock.ExpectPrepare("^SELECT\s+name FROM users$").ExpectQuery()
// ✅ 若是 INSERT/UPDATE,应使用 ExpectExec()
mock.ExpectPrepare("^INSERT INTO users.*$").ExpectExec()
// ✅ 若是单行查询(如 SELECT ... LIMIT 1),可用 ExpectQueryRow()
mock.ExpectPrepare("^SELECT id FROM orders WHERE status = \?$").ExpectQueryRow()
⚠️ 注意事项:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 正则表达式需精确匹配实际传入
Prepare()的 SQL 字符串(注意空格、换行、参数占位符?); -
ExpectPrepare()的正则不支持全字符串锚定自动添加^$,务必显式书写^...$以避免意外匹配; - 调用
db.Prepare()后,必须实际调用stmt.Query()/stmt.Exec()等方法,否则ExpectQuery()等子期望不会被触发; - 早期
go-sqlmock v1.x(如 v1.3.0 及之前)存在 bug:ExpectPrepare()单独调用不生效(issue #50)。该问题已在 v1.4.0+ 修复,新版中ExpectPrepare("^...$")可独立作为有效期望(但仍推荐链式调用以明确意图)。
? 最佳实践总结:
- 始终链式调用
ExpectQuery()/ExpectExec()等,使期望语义完整; - 使用
t.Cleanup(func(){ db.Close() })替代手动defer db.Close(),更符合测试生命周期管理; - 在测试末尾强制调用
mock.ExpectationsWereMet(),确保所有期望均已满足; - 升级至
go-sqlmock >= v1.4.0,享受更健壮的 Prepare 校验能力。
通过规范使用 ExpectPrepare().ExpectQuery() 模式,你能精准捕获 SQL 拼写错误、表名误写、字段缺失等常见问题,真正让数据库层测试发挥守门人作用。










