能,但仅限验证SQL调用行为;Sqlmock不执行SQL、不校验语法,只检查Query/Exec等方法调用是否匹配Expect,且必须严格对应动词、事务状态和正则匹配规则。

Sqlmock 能不能真正替代真实数据库做单元测试
能,但只在“验证 SQL 行为”这个目标下成立。Sqlmock 不执行 SQL,也不校验语法或字段类型,它只检查你的代码是否按预期调用了 Query、Exec、Prepare 等方法,并返回你预设的响应。一旦测试里出现 tx.Commit() 但没提前 ExpectCommit(),或者 SQL 字符串里多了一个空格导致匹配失败,测试就挂——这不是数据库问题,是挡板契约没对齐。
- 它不连接任何数据库,启动快、无依赖,适合 CI 环境
- 无法捕获如 “WHERE id = ?” 传入 nil 导致的空指针 panic,这类逻辑错误仍需靠代码审查或集成测试暴露
- 如果你用
database/sql的QueryRowContext,就必须用ExpectQuery;用ExecContext就得配ExpectExec,动词必须严格对应
初始化 mock DB 时为什么必须用 sqlmock.New() 而不是 sql.Open
因为 sqlmock.New() 返回的是 *sql.DB + 内置 mock 驱动的组合体,底层驱动已被替换为内存态的 fake driver;而 sql.Open("postgres", "...") 会真连数据库,哪怕连接字符串写错也只在首次查询时报错,根本绕不开网络层。直接传 sql.Open 的实例进测试,Sqlmock 完全感知不到,所有 ExpectXXX 都无效。
- 正确写法:
db, mock, err := sqlmock.New()<br>if err != nil {<br> panic(err)<br>}<br>// 然后把 db 注入你的 service - 别在测试中复用全局
*sql.DB实例,每个测试用例都该有独立mock控制器,避免 Expect 残留干扰下一个测试 - 记得在测试末尾调
mock.ExpectationsWereMet(),否则即使 SQL 没被触发,测试也通过——这是最常漏掉的断言
如何让 Sqlmock 匹配带占位符的动态 SQL
默认情况下,Sqlmock 对 SQL 字符串做精确匹配,但 Go 的 database/sql 在预处理时会把 ? 替换成 、(PostgreSQL)或保持 ?(MySQL),导致你写的 "SELECT * FROM users WHERE id = ?" 和实际发出的 SQL 不一致。解决方案是用正则匹配,而不是字符串相等。
- 用
mock.ExpectQuery(`^SELECT \* FROM users WHERE id = \?$`),注意转义特殊字符 - 如果 SQL 含换行或多余空格,用
regexp.QuoteMeta包一层再拼接,比如:sql := `SELECT name FROM orders<br>WHERE user_id = ? AND status = ?`<br>mock.ExpectQuery(regexp.QuoteMeta(sql))
- 避免在 SQL 中拼接变量(如
"SELECT * FROM " + table),这会让正则难写且易出错;测试时可先用mock.ExpectQuery().WillReturnRows()配合sqlmock.NewRows()构造返回数据
测试事务时 ExpectBegin/ExpectCommit 必须成对出现
Sqlmock 把事务当作状态机:调 db.Begin() → 触发 ExpectBegin();调 tx.Commit() → 必须已声明 ExpectCommit();调 tx.Rollback() → 同理要 ExpectRollback()。漏掉任意一个,ExpectationsWereMet() 就失败。
立即学习“go语言免费学习笔记(深入)”;
- 典型陷阱:代码里写了
if err != nil { tx.Rollback() },但测试只覆盖成功路径,没 mock rollback 场景,结果测试跑通,上线后 panic - 建议每个事务测试都显式覆盖 commit 和 rollback 分支,哪怕只是
mock.ExpectRollback().WillReturnError(fmt.Errorf("rollback failed")) - 不要在测试里调
tx.Close()或db.Close(),Sqlmock 不关心连接生命周期,关了反而可能干扰后续 Expect
Sqlmock 的核心约束很朴素:你写的 Expect 是契约,代码执行是履约。契约没写全,或代码绕过了契约(比如用 raw driver 绕过 database/sql),测试就失去意义。真正难的从来不是怎么写 Expect,而是想清楚——这一层测试,到底要守住哪条边界。


















