
gosqlmock是一个用于模拟数据库 /sql 驱动的库核心作用是在不依赖真实数据库实例的情况下对数据库相关逻辑进行单元测试避免测试过程中操作真实数据、产生脏数据或依赖数据库服务可用性。优点解除真实数据库依赖保证测试独立、稳定、无脏数据精准控制数据库行为覆盖常规 / 异常全量测试场景兼容database/sql标准库和主流 ORM无侵入式集成严格验证预期行为提升测试准确性发现隐藏问题轻量级无冗余内存级执行测试性能优异支持正则匹配灵活适配复杂 SQL 场景1.安装github地址go get github.com/DATA-DOG/go-sqlmock2.使用示例相关代码在gitee代码仓库的示例代码中仓库地址请看博客开头1查询mockprice_policy.gopackage modelimport (gorm.io/gorm)type PricePolicy struct {gorm.ModelCatogory string gorm:type:varchar(64) json:catogory label:收费类型Title string gorm:type:varchar(64) json:title label:标题Price uint64 gorm:type:int(5) json:httptest_demo label:价格ProjectNum uint64 json:project_num label:项目数量ProjectMember uint64 json:project_member label:项目成员人数ProjectSpace uint64 json:project_space label:每个项目空间 help_text:单位是MPerFileSize uint64 json:per_file_size label:单文件大小 help_text:单位是M}// GetAllBlog 查询所有博客信息func GetAllBlog() PricePolicy {var allBlog PricePolicyDB.Find(allBlog)return allBlog}// TypeBlog 根据类型查找博客func TypeBlog(tyb string) PricePolicy {var typeBlog PricePolicyDB.Model(PricePolicy{}).Where(type?, tyb).Find(typeBlog)return typeBlog}// TopBlog 置顶博客查询func TopBlog(top string) PricePolicy {var topBlog PricePolicyDB.Model(PricePolicy{}).Where(top?, top).Find(topBlog)return topBlog}price_policy_test.gopackage modelimport (github.com/DATA-DOG/go-sqlmockgithub.com/stretchr/testify/assertgorm.io/driver/mysqlgorm.io/gormtestingtime)// TestGetAllBlog GetAllBlog 函数单元测试func TestGetAllBlog(t *testing.T) {// 步骤1创建 sqlmock 模拟连接内存级无真实数据库依赖// sqlmock.New() 返回 mockDB(*sql.DB)、mock(sqlmock.Sqlmock)、errormockSqlDB, mock, err : sqlmock.New()assert.NoError(t, err, 创建 sqlmock 连接失败)defer mockSqlDB.Close() // 测试结束关闭模拟连接// 步骤2将 sqlmock 连接适配为 GORM 可用的 DB 实例// 关键使用 gorm mysql 驱动传入 mock 的 *sql.DB 实例gormDB, err : gorm.Open(mysql.New(mysql.Config{Conn: mockSqlDB, // 绑定 sqlmock 的连接SkipInitializeWithVersion: true, // 跳过 MySQL 版本检测模拟连接无需版本信息}), gorm.Config{})assert.NoError(t, err, GORM 绑定 sqlmock 连接失败)// 步骤3替换全局 DB 为 mock 的 GORM DB核心让业务函数使用 mock 连接DB gormDB// 步骤4构造模拟返回数据与 PricePolicy 字段对应需包含 gorm.Model 的默认字段expectedPolicy : PricePolicy{Model: gorm.Model{ID: 1,CreatedAt: time.Time{}, // 测试中可忽略时间字段若需精确匹配可赋值 time.Time 实例UpdatedAt: time.Time{},DeletedAt: gorm.DeletedAt{},},Catogory: 个人版,Title: 基础收费套餐,Price: 99,ProjectNum: 5,ProjectMember: 10,ProjectSpace: 1024,PerFileSize: 50,}// 步骤5设置 sqlmock 预期关键匹配 GORM 自动生成的 SQL 语句// GORM 的 Find(allBlog) 会生成 SELECT * FROM price_policies 语句表名默认是结构体小写复数// 使用正则匹配忽略无关空格和潜在的字段顺序差异rows : sqlmock.NewRows([]string{id, created_at, updated_at, deleted_at,catogory, title, httptest_demo, project_num,project_member, project_space, per_file_size,}).AddRow(expectedPolicy.ID, expectedPolicy.CreatedAt, expectedPolicy.UpdatedAt, expectedPolicy.DeletedAt,expectedPolicy.Catogory, expectedPolicy.Title, expectedPolicy.Price, expectedPolicy.ProjectNum,expectedPolicy.ProjectMember, expectedPolicy.ProjectSpace, expectedPolicy.PerFileSize,)// 预设查询预期匹配 GORM 生成的 SELECT 语句mock.ExpectQuery(^SELECT \\* FROM price_policies).WillReturnRows(rows) // 设置查询返回的模拟数据// 步骤6执行待测试函数_ GetAllBlog()// 步骤7验证结果// 关键验证所有 sqlmock 预期都已被执行无遗漏、无多余操作assert.NoError(t, mock.ExpectationsWereMet(), 存在未满足的 sqlmock 预期)}命令行执行命令go test -run ^TestGetAllBlog$ -cover结果PS D:\wyl\workspace\go\tracer\model go test -run ^TestGetAllBlog$ -coverPASScoverage: 13.6% of statementsok tracer/model 0.082s2增删改mock这个需要注意gorm在执行增删改动作底层使用了事务操作所以代码中没有使用到事务时在mock中也要mock事务操作user.gopackage modelimport (gorm.io/gorm)// UserInfo 用户表type UserInfo struct {gorm.ModelUserName string gorm:type:varchar(32);unique json:user_name label:用户名Password string gorm:size:60 json:password label:密码Phone string gorm:size:11;unique json:phone label:手机号Email string gorm:size:32;unique json:email label:邮箱}// GetAllUser 查询所有用户信息func GetAllUser() (users []UserInfo, err error) {err DB.Model(UserInfo{}).Find(users).Errorreturn}func UpdateUserPhone(id int64, phone string) (err error) {err DB.Model(UserInfo{}).Where(id ?, id).Updates(map[string]interface{}{phone: phone,}).Errorreturn}user_test.gopackage modelimport (errorsgithub.com/DATA-DOG/go-sqlmockgithub.com/stretchr/testify/assertgorm.io/driver/mysqlgorm.io/gormtesting)// TestUpdateUserPhone_success 测试场景1更新手机号【成功】- 正常更新匹配ID的用户手机号func TestUpdateUserPhone_success(t *testing.T) {// 步骤1创建 sqlmock 模拟连接内存级无真实数据库依赖// sqlmock.New() 返回 mockDB(*sql.DB)、mock(sqlmock.Sqlmock)、errormockSqlDB, mock, err : sqlmock.New()assert.NoError(t, err, 创建 sqlmock 连接失败)defer mockSqlDB.Close() // 测试结束关闭模拟连接// 步骤2将 sqlmock 连接适配为 GORM 可用的 DB 实例// 关键使用 gorm mysql 驱动传入 mock 的 *sql.DB 实例gormDB, err : gorm.Open(mysql.New(mysql.Config{Conn: mockSqlDB, // 绑定 sqlmock 的连接SkipInitializeWithVersion: true, // 跳过 MySQL 版本检测模拟连接无需版本信息}), gorm.Config{})assert.NoError(t, err, GORM 绑定 sqlmock 连接失败)// 步骤3替换全局 DB 为 mock 的 GORM DB核心让业务函数使用 mock 连接DB gormDB// 测试入参testID : int64(1)testPhone : 13800138000// 核心mock断言匹配GORM生成的update语句// ^ 匹配开头 $ 匹配结尾 \? 是sql占位符的正则转义mock.ExpectBegin()mock.ExpectExec(^UPDATE user_infos SET phone\\?,updated_at\\? WHERE id \\? AND user_infos.deleted_at IS NULL$).WithArgs(testPhone, sqlmock.AnyArg(), testID). // phone入参值, updated_at是gorm自动填充用任意值匹配, id入参值WillReturnResult(sqlmock.NewResult(testID, 1)) // 返回执行结果影响行数1行mock.ExpectCommit()// 执行待测试的业务函数err UpdateUserPhone(testID, testPhone)// 断言执行无错误if err ! nil {t.Errorf(更新手机号失败预期无错误实际错误%v, err)}}