用 sqlmock 测试数据层时,必须用 sqlmock.New() 创建 mock DB 并传给业务函数,通过 ExpectQuery/ExpectExec 预设 SQL 与参数,调用 ExpectationsWereMet() 校验,事务需显式 ExpectBegin/ExpectRollback,避免拼接 SQL 断言,集成测试需隔离运行。

用 sqlmock 拦截真实数据库调用
测试数据层时,绝不能依赖本地或测试环境的真实数据库——它会让测试变慢、不可靠、难并行。最常用方案是用 sqlmock 替换 *sql.DB,模拟查询、插入、事务等行为。它不执行 SQL,只校验你传入的 SQL 语句、参数、调用顺序是否符合预期。
关键点:
-
sqlmock必须在测试初始化时通过sqlmock.New()创建,并用sqlmock.ExpectQuery()或sqlmock.ExpectExec()预设响应 - 传给业务函数的
*sql.DB必须是sqlmock返回的 mock DB(底层是*sql.DB,但驱动被替换) - 测试末尾务必调用
mock.ExpectationsWereMet(),否则未匹配的期望会静默失败 - 注意
sqlmock默认不支持ORDER BY中的列别名、子查询别名等“非标准”写法,容易因空格或换行不一致导致匹配失败
测试事务逻辑必须显式控制 Begin/Commit/Rollback
很多 Go 数据层封装了事务(比如用 tx, _ := db.Begin()),但若测试中没 mock Begin 方法,实际会连到真实数据库。正确做法是:让数据访问函数接收 context.Context 和 Querier 接口(而非硬编码 *sql.DB),并在测试中传入 mock 的 *sql.Tx。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 定义
type Querier interface { QueryRowContext(...); ExecContext(...) },让*sql.DB和*sql.Tx都实现它 - 在测试中用
sqlmock.New()创建 mock DB 后,调用mock.ExpectBegin(),再用mockDB.Begin()获取 mock tx - 对 tx 的操作(如
tx.QueryRowContext())需单独用mock.ExpectQuery()绑定到该 tx,否则会报 “no query expectations were met” - 别忘了覆盖
Rollback场景:调用mock.ExpectRollback(),然后手动触发错误路径
避免在测试里拼接 SQL 字符串做断言
有人习惯在测试中写 assert.Contains(t, actualSQL, "INSERT INTO users"),这非常脆弱。SQL 格式受 driver、ORM 层(如 sqlx)、占位符类型( vs ?)影响,且无法校验参数绑定是否正确。
更可靠的做法是:
- 用
mock.ExpectQuery("INSERT INTO users").WithArgs("alice", 25)显式声明 SQL 模式和参数 - SQL 模式支持正则(如
regexp.QuoteMeta("INSERT INTO users") + `.*`),但优先用字面量匹配,减少不确定性 - 如果业务用了
sqlx.NamedExec,记得用mock.ExpectExec().WithArgs(...),而不是按顺序猜命名参数的展开顺序 - 注意
sqlmock对多语句(如分号分隔)默认不支持,需启用sqlmock.WithMultiStatement(true)
真实数据库集成测试留作可选补充,但要隔离运行
单元测试用 sqlmock 足够验证逻辑,但某些场景仍需跑真实 SQL:比如复杂 join、JSON 字段解析、PostgreSQL 特有函数(jsonb_set)、索引行为等。这类测试不能和单元测试混跑,必须明确标记为 integration。
执行要点:
- 用
if testing.Short() { t.Skip("skipping integration test") }开头,配合go test -short自动跳过 - 连接字符串从环境变量读取(如
os.Getenv("TEST_DB_URL")),禁止硬编码 - 每个测试用独立 schema 或随机表名(如
"test_users_" + uuid.NewString()),避免并发冲突 - 务必在
defer中清理资源(db.Exec("DROP TABLE ...")),哪怕测试 panic 也要执行
mock 覆盖不了驱动层行为,比如 lib/pq 解析 timestamp with time zone 的精度丢失,这种只能靠真实库测出来。别省这一步,但别让它拖慢日常开发节奏。










