sqlmock只能拦截*sql.DB调用,需通过gorm.DB()获取底层实例并注入;ExpectQuery用于SELECT,ExpectExec用于增删改;多条SQL须严格按执行顺序Expect;gomock与sqlmock分层协作,前者mock接口,后者mock数据库驱动。

用 sqlmock 拦截 *sql.DB 调用,别碰 gorm.DB
sqlmock 只能 mock *sql.DB 类型的底层驱动,不能直接 mock gorm.DB。如果你用的是 GORM,得先拿到它内部的 *sql.DB 实例(通过 db.DB()),再传给 sqlmock 创建 mock 对象。否则调用 db.Create() 或 db.First() 时,sqlmock 完全收不到 SQL,测试就变成“假跑”。
常见错误现象:Expected query, not executed 或测试直接 panic——十有八九是没把 sqlmock 的 *sql.DB 注入到 GORM 实例里。
- 正确做法:用
sqlmock.New()创建mockDB,再用gorm.Open(gormmysql.New(gormmysql.Config{Conn: mockDB}))构建测试用的*gorm.DB - 别在 test 文件里写
defer db.Close():sqlmock 返回的*sql.DB是假的,Close()会 panic - 若用
db.Unscoped().Where(...).Delete()等软删除操作,SQL 语句含WHERE deleted_at IS NULL,mock 时得写全条件,否则匹配失败
ExpectQuery 和 ExpectExec 的区别必须分清
ExpectQuery() 用于 SELECT,ExpectExec() 用于 INSERT/UPDATE/DELETE —— 混用会导致测试失败且报错不直观(比如 expected query but got exec)。GORM 的链式调用可能隐式触发不同语句类型,例如 db.First(&u) 是 SELECT,db.Save(&u) 是 UPDATE 或 INSERT,得按实际执行的 SQL 类型配对 Expect。
-
ExpectQuery("SELECT.*FROM users").WillReturnRows(...):返回模拟行数据,注意字段顺序和类型要跟 struct tag 匹配 -
ExpectExec("INSERT INTO users").WillReturnResult(sqlmock.NewResult(1, 1)):返回插入的 ID 和影响行数 - 调试时可临时加
sqlmock.New(sqlmock.QueryMatcherEqual)改为字符串精确匹配,避免正则误判
Mock 多条 SQL 时顺序敏感,别依赖随机执行
sqlmock 默认严格按 Expect 声明顺序校验 SQL 执行流。如果 handler 中先查用户、再查订单、最后更新用户状态,那 Expect 也得按这个顺序写,颠倒或漏掉任意一条都会 panic。
容易踩的坑:
- 用
db.Transaction()时,事务内多条语句必须连续 Expect,中间不能穿插其他 SQL - GORM 的 Preload 会生成 JOIN 查询,不是两条独立 SELECT,Expect 时得写完整 JOIN 语句,不能只 mock 单表
- 没显式开启事务但用了
db.Session(&gorm.Session{NewDB: true}),也会导致 SQL 执行顺序变化,需同步调整 Expect 顺序
gomock 和 sqlmock 不是二选一,而是分层协作
sqlmock 负责 DAO 层(即 GORM 或原生 SQL 封装层),gomock 负责 service 层对 repository 接口的依赖。比如你有个 UserRepository 接口定义了 FindByID(id string) (*User, error),那就该用 gomock 生成 mock 实现;而这个实现内部若调用 db.First(),才轮到 sqlmock 拦截底层 SQL。
关键约束:
-
UserRepository接口必须定义在被测模块外(如repo/user_repo.go),否则mockgen无法扫描生成 - gomock 的 mock 对象要提前
On("FindByID", "123").Return(&User{ID: "123"}, nil),不然调用时 panic - 别在同一个测试里既用 gomock 又手动调用真实 GORM 方法——依赖没隔离干净,SQL 还是会真执行
真正难的不是写 Expect,而是让 DAO 层暴露可替换的 *sql.DB 或 repository 接口;如果 handler 直接 new 一个全局 gorm.DB,那 sqlmock 和 gomock 都无从下手。











