sqlmock.new() 返回 *sql.db 是为保持业务代码零修改,仅替换底层驱动;expectquery/expectexec 匹配最终发送的sql字符串,事务需显式 expectbegin/expectcommit 或 expectrollback,结尾必须调用 expectationsweremet() 校验。

sqlmock.New() 为什么返回 *sql.DB 而不是 mock 对象?
因为 sqlmock 的设计目标是「不改业务代码」——它只替换 sql.Open() 返回的 *sql.DB,让测试时所有 db.Query()、db.Exec() 等调用自动路由到 mock 行为。你拿到的仍是标准 *sql.DB,只是底层驱动被替换了。
常见错误现象:
• 直接对 sqlmock 实例调用 Query() 报错:它没有这个方法
• 忘记把 mock *sql.DB 注入到被测函数,结果还是连了真实数据库
- 必须用
sqlmock.New()创建 mock DB,并传给被测逻辑(比如通过构造函数或参数注入) - 不要试图调用
mock.ExpectQuery()之后再手动执行查询;expect 是声明式断言,执行仍需走db.Query() - mock 对象本身要调用
ExpectationsWereMet()结尾校验,否则预期未触发也不会报错
ExpectQuery() 和 ExpectExec() 的参数到底匹配什么?
它们匹配的是「最终发送给数据库的 SQL 字符串」,不是 go 源码里的字符串字面量。这意味着:
- 占位符会被展开(如
?$1→$1),但?风格占位符(mysql)和$1(postgresql)不会自动转换,必须和实际 driver 一致 - 换行、多余空格、大小写不影响匹配(默认开启正则模糊匹配),但表名/字段名拼写错误会直接失败
- 如果用了
db.Query("select * FROM users WHERE id = ?", id),expect 里就得写mock.ExpectQuery("SELECT.*users.*id")或精确写mock.ExpectQuery("SELECT * FROM users WHERE id = \?")
性能影响:正则匹配开销极小,但过度宽泛(如 .*)会让断言失去意义,掩盖 SQL 拼写错误。
立即学习“go语言免费学习笔记(深入)”;
事务测试中 sqlmock.Transaction() 容易漏掉哪三步?
mock 事务不是自动开启的,必须显式模拟 BEGIN / COMMIT / ROLLBACK 三个动作,缺一不可。否则 tx.Commit() 会 panic 或静默失败。
- 先调用
mock.ExpectBegin(),对应db.Begin() - 再对返回的
*sql.Tx调用ExpectQuery()或ExpectExec(),注意此时 SQL 是在事务上下文中执行 - 最后必须调用
mock.ExpectCommit()或mock.ExpectRollback(),否则ExpectationsWereMet()会失败
容易踩的坑:
• 忘记 ExpectBegin(),导致 db.Begin() 返回 nil 或 panic
• 在事务里用了未 expect 的 SQL,mock 不知道怎么响应
• tx.Rollback() 后又调用了 tx.Commit(),但只 expect 了 rollback —— 这会触发未满足期望错误
测试完不调用 sqlmock.AssertExpectations() 会怎样?
什么都不会报错,但你的测试是假阳性。mock 允许「没被调用的 expect」静默通过,只有调用 mock.AssertExpectations()(或更常见的 mock.ExpectationsWereMet())才会检查是否所有声明的 SQL 行为都被真实触发。
典型场景:
• 你写了 mock.ExpectQuery("SELECT name").WillReturnRows(rows),但被测函数根本没查 name 字段 → 测试仍绿
• 你 expect 了 2 次 INSERT,但实际只执行了 1 次 → 不报错,直到加了断言
- 务必在测试末尾加
assert.NoError(t, mock.ExpectationsWereMet())(用 testify/assert)或if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } - 如果用
t.Cleanup()注册 cleanup 函数,别忘了里面也要调用ExpectationsWereMet(),否则 defer 时机可能错过
最常被忽略的是:mock 对象生命周期和测试函数绑定,一旦测试函数 return,未验证的 expect 就永远失效了。