You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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 INTO users.*就可以匹配任意字段的users表插入语句,不需要完整写全所有字段
  • 如果开启了Gorm的软删除功能,需要注意生成的SQL会多deleted_at IS NULL的条件,匹配时要加上对应逻辑

内容的提问来源于stack exchange,提问作者Md. Abu Farhad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.11 19:33:03