sqlmock.new() 返回的 sql.db 必须传给被测函数,不能在函数内调用 sql.open;否则 mock 失效,连接真实数据库。正确做法是函数接收 sql.db 参数,测试时传入 mock.db,并确保 expectquery 字符串与实际 sql 完全一致。

sqlmock.New() 返回的 *sql.DB 必须传给被测函数,不能在函数里重开连接
GoLand 本身不干预 sqlmock 的行为,但你常会在测试里写错这一步:在被测函数里硬编码 sql.Open,导致 mock 完全没生效。真实数据库照样连上,测试变慢、不稳定,还污染数据。
正确做法是让函数接收 *sql.DB 参数(或封装成接口),测试时把 sqlmock.New() 返回的 DB 实例传进去。例如:
func GetUser(db *sql.DB, id int) (string, error) {
row := db.QueryRow("SELECT name FROM users WHERE id = ?", id)
// ...
}
测试中必须这样调用:
db, mock, _ := sqlmock.New()
defer db.Close()
mock.ExpectQuery("SELECT name FROM users").WithArgs(123).WillReturnRows(
sqlmock.NewRows([]string{"name"}).AddRow("alice"),
)
GetUser(db, 123) // ← 关键:传 mock DB,不是 sql.Open 出来的
mock.ExpectationsWereMet()
- 如果函数内部调用了
sql.Open,mock 就完全失效 —— GoLand 跑测试时会连真实库,报错或超时都可能发生 - 接口抽象更稳妥:定义
type Querier interface { QueryRowContext(...); ExecContext(...) },让*sql.DB和*sql.Tx都能注入 - GoLand 的代码跳转和变量提示对 mock.DB 和真 DB 一样有效,别被 IDE 表象误导
ExpectQuery 匹配失败?检查占位符风格和空格换行
GoLand 运行测试时 panic 报 there is a remaining expectation which was not matched,90% 是 mock.ExpectQuery() 字符串和实际执行的 SQL 对不上。不是逻辑错,是字面量错。
PostgreSQL 驱动把 ? 自动转成 ,MySQL 保留 ?;SQLMock 默认做全字符串精确匹配,多一个空格、少一个换行、WHERE 前后多两个空格,都会失败。
GoLand 2026.1.1 是 2026.1 发布后的首个维护修正版本,适合已经开始体验 2026.1 新功能并希望同步补丁的开发者。它更适合用于入门项目、现有项目迁移测试和 IDE 行为验证。
- PostgreSQL 用户:业务代码用
$1,测试就得写mock.ExpectQuery(`SELECT * FROM users WHERE id = $1`) - MySQL 用户:别写死
?,改用正则 +regexp.QuoteMeta,比如mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM users") + ` WHERE id = \?`) - 想忽略参数值差异?用
WithArgs(sqlmock.AnyArg()),而不是靠 SQL 字符串猜参数位置 - 别在 ExpectQuery 里写
ORDER BY u.name却执行ORDER BY users.name—— 别名、表别名、大小写全要一致
Scan 或 StructScan 总是空或 panic?列头声明必须严格对齐
GoLand 调试时看到 sql: expected 3 destination arguments, not 2 或结构体字段全是零值,问题不在 SQL 语句,而在 sqlmock.NewRows() 的列定义。
NewRows([]string{"id", "name"}) 声明的顺序,必须和 Scan(&id, &name) 的参数顺序、以及结构体字段 db tag 完全一致。时间字段传字符串会静默失败,不是报错。
- 列名必须和
dbtag 一模一样:type User struct { ID int `db:"user_id"` }→NewRows([]string{"user_id"}) - 时间字段别传
"2024-01-01",用time.Now()或sqlmock.NewNullTime(t, true) - 空结果集也要调
WillReturnRows(sqlmock.NewRows([]string{"id"})),不能只写WillReturnRows(nil) - StructScan 失败时,先打印
rows.Columns()看实际列名,再比对 tag —— GoLand 的 Evaluate Expression 功能这时候很管用
事务测试卡在 Commit 不报错?ExpectBegin/ExpectCommit 必须配对且显式绑定
GoLand 跑事务测试时没报错但 mock.ExpectationsWereMet() 失败,大概率是 Begin 后的 Query/Exec 没被 mock 拦截到。Sqlmock 默认不自动跟踪事务内语句,你得手动为每条语句设期望。
事务不是“一个大 SQL”,而是独立的执行链:Begin → Query → Exec → Commit。每步都要 Expect,而且 Query/Exec 必须绑定到 *sql.Tx 上,不能只绑 *sql.DB。
- 先
mock.ExpectBegin(),再调db.Begin()得到 tx - 对 tx 的操作,如
tx.QueryRow(),要单独mock.ExpectQuery(...),不能复用 db 的 Expect - Rollback 场景必须显式
mock.ExpectRollback(),并在被测函数里触发错误路径 - 别在一个测试里复用同一个 mock 实例 —— 每次
sqlmock.New()创建新实例,GoLand 并行跑测试时尤其关键
事务内 SQL 匹配失败往往最难定位,因为错误信息不指明哪条语句漏了 Expect;建议每次只测一条语句,确认链路通了再加下一条。










