如何使用sqlmock为Gorm的DB操作编写单元测试Mock
基于sqlmock的Gorm层单元测试实现方案
一、Save方法单元测试编写
首先先安装依赖:go get github.com/DATA-DOG/go-sqlmock gorm.io/driver/mysql
然后编写测试代码,分保存成功/失败两个测试用例:
package repository import ( "testing" "your-project-path/domain" "github.com/DATA-DOG/go-sqlmock" "gorm.io/driver/mysql" "gorm.io/gorm" "github.com/stretchr/testify/assert" ) func TestUsers_Save(t *testing.T) { // 1. 初始化sqlmock和gorm DB实例 sqlDB, mock, err := sqlmock.New() assert.NoError(t, err) defer sqlDB.Close() // 初始化gorm,禁用预准备语句避免额外的prepare调用匹配报错 gormDB, err := gorm.Open(mysql.New(mysql.Config{ Conn: sqlDB, SkipInitializeWithVersion: true, }), &gorm.Config{ PrepareStmt: false, }) assert.NoError(t, err) repo := NewMySqlUsersRepository(gormDB) testUser := &domain.User{ // 按需填充测试用户字段,示例如下 Name: "test", Email: "test@example.com", } t.Run("save user success", func(t *testing.T) { // 匹配INSERT语句,正则可以根据实际gorm生成的SQL调整,测试不匹配时直接复制sqlmock打印的实际SQL即可 mock.ExpectBegin() mock.ExpectExec("INSERT INTO `users` (`name`,`email`) VALUES (?,?)"). WithArgs(testUser.Name, testUser.Email). WillReturnResult(sqlmock.NewResult(1, 1)) // 返回插入ID为1,影响行数1 mock.ExpectCommit() // 执行待测试方法 savedUser, err := repo.Save(testUser) // 断言结果 assert.Nil(t, err) assert.Equal(t, testUser.Name, savedUser.Name) assert.NoError(t, mock.ExpectationsWereMet()) // 验证所有预期的数据库调用都已执行 }) t.Run("save user failed", func(t *testing.T) { // 模拟数据库报错场景 mock.ExpectBegin() mock.ExpectExec("INSERT INTO `users` (`name`,`email`) VALUES (?,?)"). WithArgs(testUser.Name, testUser.Email). WillReturnError(gorm.ErrInvalidDB) mock.ExpectRollback() // 执行待测试方法 savedUser, err := repo.Save(testUser) // 断言结果 assert.NotNil(t, err) assert.Nil(t, savedUser) assert.Equal(t, 500, err.StatusCode) // 对应你返回的内部服务错误状态码 assert.NoError(t, mock.ExpectationsWereMet()) }) }
注意:请根据实际的domain.User字段调整INSERT语句的正则和参数列表即可。
二、其他Gorm操作的通用Mock方案
核心逻辑都是:先预判Gorm会生成的SQL、参数,然后设置对应的返回结果/错误,最后验证所有预期都被执行。常见操作的Mock示例如下:
1. 根据ID查询单条用户
假设你的方法是GetByID(id int64) (*domain.User, *errors.RestErr),Gorm代码为r.DB.Where("id = ?", id).First(&user),Mock逻辑如下:
// 成功场景 mock.ExpectQuery("SELECT * FROM `users` WHERE id = ? LIMIT 1"). WithArgs(1). WillReturnRows(sqlmock.NewRows([]string{"id", "name", "email"}).AddRow(1, "test", "test@example.com")) // 未查询到数据场景 mock.ExpectQuery("SELECT * FROM `users` WHERE id = ? LIMIT 1"). WithArgs(2). WillReturnError(gorm.ErrRecordNotFound)
2. 查询用户列表
假设Gorm代码为r.DB.Where("age > ?", 18).Find(&users),Mock逻辑如下:
mock.ExpectQuery("SELECT * FROM `users` WHERE age > ?"). WithArgs(18). WillReturnRows(sqlmock.NewRows([]string{"id", "name", "age"}). AddRow(1, "test1", 20). AddRow(2, "test2", 22))
3. 更新用户信息
假设Gorm代码为r.DB.Model(&domain.User{}).Where("id = ?", user.ID).Updates(user),Mock逻辑如下:
mock.ExpectBegin() mock.ExpectExec("UPDATE `users` SET `name`=?,`email`=? WHERE id = ?"). WithArgs(user.Name, user.Email, user.ID). WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectCommit()
4. 删除用户
假设Gorm代码为r.DB.Delete(&domain.User{}, id),Mock逻辑如下:
mock.ExpectBegin() mock.ExpectExec("DELETE FROM `users` WHERE `users`.`id` = ?"). WithArgs(1). WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectCommit()
通用注意事项
- 所有写操作(增删改)Gorm默认都会开启事务,所以需要匹配
ExpectBegin、ExpectCommit/ExpectRollback - 正则匹配SQL时可以用
.*简化匹配,比如INSERT INTOusers.*就可以匹配任意字段的users表插入语句,不需要完整写全所有字段 - 如果开启了Gorm的软删除功能,需要注意生成的SQL会多
deleted_at IS NULL的条件,匹配时要加上对应逻辑
内容的提问来源于stack exchange,提问作者Md. Abu Farhad
相关产品推荐
相关产品推荐

