不能直接 mock GORM 实例,因其 v2 版本未暴露可替换驱动接口,且初始化时自动执行依赖真实 DB 的元查询(如 SELECT DATABASE()),mock 后字段标签、软删除等特性失效;正确做法是用 sqlmock.New() 创建 *sql.DB 并传给 gorm.Open(),显式 Expect 所有 GORM 生成的 SQL(含元查询与事务语句),最后调用 mock.AssertExpectations(t)。

直接用 sqlmock 替换 GORM 的底层 *sql.DB,而不是 mock GORM 本身——GORM 没法被直接 mock,但它的驱动层可以被替换。
为什么不能直接 mock GORM 实例
GORM v2(gorm.io/gorm)内部不暴露可替换的数据库驱动接口,它封装了 *sql.DB 并做了大量元查询(比如 SELECT DATABASE()、SHOW TABLES)。如果你试图用 gomock 或接口抽象去 mock 一个 *gorm.DB,会发现:
- 绝大多数方法返回值依赖真实 DB 状态,mock 后行为不可控
- GORM 初始化时会自动执行元查询,这些语句必须被
sqlmock显式 Expect,否则测试 panic - 字段标签、软删除、时间戳等特性在 mock 下不会生效,容易掩盖逻辑缺陷
正确做法:用 sqlmock.New() 构造 *sql.DB,再传给 gorm.Open()
核心是让 GORM 使用你提供的 mock *sql.DB,而非真实连接。关键步骤如下:
- 调用
sqlmock.New()创建 mock DB 实例 - 用
gorm.Open(mysql.New(mysql.Config{Conn: mockDB}))(v2)或gorm.Open(mysql.New(), &gorm.Config{...})初始化 GORM - 对每一条 GORM 生成的真实 SQL(含元查询),提前用
mock.ExpectQuery()或mock.ExpectExec()声明预期 - 务必在测试末尾调用
mock.AssertExpectations(t),否则漏匹配的 SQL 不会报错
示例片段:
func TestCreateOrder(t *testing.T) {
mockDB, mock, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
defer mockDB.Close()
<pre class='brush:php;toolbar:false;'>gormDB, err := gorm.Open(mysql.New(mysql.Config{Conn: mockDB}), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
// Expect GORM 的元查询(v2 默认会查)
mock.ExpectQuery(`SELECT DATABASE\(\)`).WillReturnRows(sqlmock.NewRows([]string{"DATABASE()"}).AddRow("test"))
// Expect 实际业务 SQL
mock.ExpectExec(`INSERT INTO orders`).WithArgs("2026-10-12 19:50:00", "pending").WillReturnResult(sqlmock.NewResult(1, 1))
order := Order{Status: "pending", CreatedAt: time.Now()}
result := gormDB.Create(&order)
if result.Error != nil {
t.Fatal(result.Error)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Error(err)
}}
常见踩坑点:元查询和事务处理
GORM v2 在初始化、事务开启、甚至 First() 调用时,都会悄悄发额外 SQL。不处理就会测试失败:
-
SELECT DATABASE():首次连接时必发,必须 Expect -
SELECT .* FROM information_schema.columns WHERE table_name = ?:首次访问新表时可能触发(尤其带AutoMigrate) -
BEGIN/COMMIT/ROLLBACK:如果 DAO 方法用了Transaction(),对应语句也得 Expect - 参数顺序错位:GORM 绑定参数顺序 ≠ 你写的 SQL 字面量顺序(尤其含子查询或函数时),建议用
mock.ExpectQuery(...).WillReturnRows(...)配合日志确认实际 SQL
调试技巧:启用 GORM 日志,把真实 SQL 打出来再复制进 Expect —— 在 gorm.Config 中加 Logger: logger.Default.LogMode(logger.Info)。
DAO 层要支持 mock,代码结构得改
硬编码 gorm.Open(...) 的 DAO 无法注入 mock DB。必须把 *gorm.DB 作为参数传入,或通过接口抽象:
- 推荐方式:DAO 方法接收
*gorm.DB为第一个参数,如func (o *OrderDAO) Create(db *gorm.DB, order *Order) error - 避免全局变量
_DB *gorm.DB,它会让测试间状态污染 - 不要在 DAO 初始化时做
AutoMigrate,它会触发真实 DDL,mock 下必然失败;移到应用启动阶段或跳过
真正的难点不在写 mock,而在于让业务代码“愿意被测”——DAO 必须无状态、可替换依赖、不隐藏 SQL 行为。


















