sqlmock是gin单元测试中最常用的db模拟器,因为它包装*sql.db拦截query/exec等调用,校验sql语句、参数及错误路径,启动零成本且与gin解耦;需用sqlmock.new()创建mock db,显式声明每条expectquery/expectexec,末尾必须调用expectationsweremet()校验。

直接用真实数据库跑单元测试,等于给 CI 加了一颗定时炸弹——慢、不稳定、易污染、难并行。必须用模拟器隔离 DB 层。
sqlmock 为什么是 Gin 单元测试里最常用的 DB 模拟器
sqlmock 不是“假装有个数据库”,而是包装 *sql.DB,在调用 Query、Exec、QueryRow 时拦截并校验 SQL 是否符合预期。它不依赖任何外部服务,启动零成本,适合验证 DAO 层逻辑是否生成了正确的语句、参数是否传对、错误路径是否触发。
- 它和 Gin 完全解耦,只关心你传给 repository 的
*sql.DB实例 - 必须用
sqlmock.New()创建 mock DB,不能用sql.Open连真实库再塞进去 - 每条预期 SQL 都要显式声明,比如
mock.ExpectQuery("SELECT.*FROM users"),否则运行时报"there is a remaining expectation which was not matched" - 默认参数匹配是严格相等,遇到 UUID、时间戳等动态值,得用
sqlmock.AnyArg()
gomock 和 sqlmock 怎么分工不打架
两者解决不同层级的问题:sqlmock 拦截 SQL 执行细节,gomock 模拟接口行为。DAO 层用 sqlmock,service 层用 gomock —— 这是标准分层隔离法。
- repository 接口(如
repo.UserRepo)必须定义在被测模块外,否则mockgen无法生成实现 - service 测试中,把 gomock 生成的
mockUserRepo注入 handler 或 usecase,让它返回预设数据,完全绕过 DB - sqlmock 默认开启严格模式,未命中的查询会 panic;调试时可临时换成
sqlmock.New(sqlmock.QueryMatcherEqual)改为宽松匹配 - 别在 test 文件里写
defer db.Close()—— sqlmock 的db是假的,Close()会 panic
测试带路径参数或表单数据的接口时,sqlmock 前还要过 Gin 上下文这关
sqlmock 只管 DB 调用,但 Gin handler 里常有 c.Param("id")、c.ShouldBindJSON()、c.PostForm("email") 这类操作。如果上下文没初始化好,还没走到 DB 就 panic 了。
- 必须用
gin.CreateTestContext(w)创建c,不能用http.ServeHTTP,否则中间件、路由匹配、c.Param全失效 - 手动设置
c.Request.Header.Set("Content-Type", "application/json"),否则c.ShouldBindJSON静默失败 - 测试
/users/:id这类路由,必须显式赋值c.Params = gin.Params{{Key: "id", Value: "123"}},不然c.Param("id")返回空字符串 - 若 handler 内部调用了
c.MustGet("db").(*sql.DB),记得提前c.Set("db", mockDB)
真正容易被忽略的是:sqlmock 的期望必须在测试函数结束前调用 mock.ExpectationsWereMet() 校验,否则即使 SQL 写错了也不会报错 —— 它只是“没检查”,不是“通过”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











