sqlmock无法Mock GORM连接池,因其仅接管sql.DB执行逻辑,不干预MaxOpenConns等池参数;测试应使用sqlmock.New()创建sql.DB并传入gorm.Open()构造无池实例,禁用PrepareStmt以确保SQL匹配。

sqlmock 不能直接 Mock GORM 连接池本身
你没法 mock *gorm.DB 的连接池行为(比如 MaxOpenConns、MaxIdleConns),因为 sqlmock 只接管底层 *sql.DB 的执行逻辑,不干预连接池配置。GORM 的连接池参数在初始化时就生效,测试中若直接复用真实 *gorm.DB 实例,哪怕底层是 sqlmock,连接池状态仍可能被污染(例如并发测试时连接数突增导致 panic)。
真正要测的是「DAO 方法是否发出预期 SQL」,不是「连接池是否复用连接」。所以必须剥离连接池管理,让测试只面对一个干净的、可预测的 *sql.DB。
- 不要在测试里调用
gorm.Open();而是用sqlmock.New()拿到*sql.DB,再传给gorm.New()构造无连接池的测试用*gorm.DB - 禁用 GORM 内部连接池:调用
gorm.Config{PrepareStmt: false, SkipDefaultTransaction: true},避免它自动开事务或预编译干扰 sqlmock 匹配 - 如果 DAO 层接收的是
*gorm.DB,测试时别传带连接池的实例;统一从 sqlmock 创建的*sql.DB构造一个临时*gorm.DB,且不调用.DB().SetMaxOpenConns()等方法
mockgen 不适合 GORM 数据库层 Mock
你不需要、也不应该对 GORM 相关接口(比如 UserRepository)跑 mockgen。GORM 本身不是你定义的 interface,它是具体实现;而你的 DAO 层通常只是封装了 *gorm.DB 调用,没有抽象出 interface —— 这恰恰是 sqlmock 发挥作用的前提。
常见错误是试图 mock *gorm.DB 或 gorm.Session:它们不是 Go 接口,mockgen 会报 no interfaces found 或生成空文件。
- 正确做法:保持 DAO 结构体字段为
db *gorm.DB,但测试时注入由 sqlmock 驱动的*gorm.DB实例 - 如果想用 gomock,得先自己定义 interface,比如
type UserStore interface { GetByID(int) (*User, error) },再让 DAO 实现它;但这属于架构改造,不是单元测试的必选项 - 别在
_test.go文件里定义接口然后跑 mockgen ——mockgen -source=xxx_test.go不被支持,会静默失败
sqlmock.ExpectQuery 必须匹配 GORM 实际生成的 SQL
GORM v2 默认开启 PrepareStmt,会把 SELECT * FROM users WHERE id = ? 编译成预处理语句,sqlmock 默认不匹配这种形式,导致 mock.ExpectQuery() 失败。
最稳的方式是关闭预编译,并显式写出 SQL 模式(注意空格、换行、表名大小写):
db, mock, _ := sqlmock.New()
gormDB, _ := gorm.Open(mysql.New(mysql.Config{Conn: db}), &gorm.Config{
PrepareStmt: false, // 关键
})
mock.ExpectQuery(`^SELECT \* FROM "users" WHERE "users"."id" = \?$`).WithArgs(123)- GORM v2 默认用双引号包裹标识符(如
"users"),MySQL 模式下实际发的是反引号,但 sqlmock 匹配的是 GORM 输出的字符串,不是数据库实际执行的 - 用正则时,
^和$必须加,否则可能误匹配子串;\?要转义问号 - 执行完被测函数后,必须调
mock.ExpectationsWereMet(),否则即使 SQL 没命中也不会报错
defer db.Close() 和 mock.ExpectationsWereMet() 缺一不可
漏掉 defer db.Close() 会导致文件描述符泄漏,多次运行测试时卡住;漏掉 mock.ExpectationsWereMet() 则所有 SQL 校验形同虚设,测试永远通过。
-
db.Close()必须在测试函数末尾 defer,不能放在 helper 函数里 —— 否则作用域不对,关闭时机不可控 -
mock.ExpectationsWereMet()应该放在defer之后、函数返回前,确保无论是否 panic 都能校验 - 如果 DAO 方法内部开了事务,sqlmock 无法自动感知
Begin/Commit/Rollback,得手动注册mock.ExpectBegin()、mock.ExpectCommit()等
GORM 单元测试里最容易被忽略的点,是以为 sqlmock 替换了 *gorm.DB 就万事大吉 —— 实际上 GORM 的配置项(如 PrepareStmt、NamingStrategy)、SQL 输出格式、事务行为,都会让期望与实际产生偏差。每次 SQL 匹配失败,优先查 GORM 版本和配置,而不是怀疑 sqlmock。


















