gomock仅支持mock接口,不支持普通函数或结构体方法;需用mockgen显式指定-package和-destination,接口文件须可导出且方法首字母大写,测试中必须传入*gomock.controller并调用finish校验。

gomock 不能直接 mock 普通函数或结构体方法,只支持 interface;生成失败、类型未定义、调用报错,基本都卡在这条铁律上。
mockgen 命令必须显式指定 -package 和 -destination
mockgen 默认生成的包名是 mock_,但测试文件里 import 的路径末尾往往和这个不一致,导致 undefined: XxxMock。
常见错误现象:编译报 undefined: MockUserService,哪怕文件存在、路径看着对。
- 必须用
-package指定生成文件的包名,且要和import路径最后一段完全一致(比如import "myproj/mocks",那-package就得是mocks) - 必须用
-destination明确写死输出路径,避免生成到 GOPATH 或模块外的不可控位置 - 接口定义文件必须可导出(文件名不能以
_或.开头),且接口内方法名首字母必须大写
示例命令:
mockgen -source=service/user_service.go -destination=mocks/mock_user_service.go -package=mocks
测试中初始化 Mock 必须配对使用 *gomock.Controller
NewMockXxx 构造函数第一个参数永远是 *gomock.Controller,不是可选的。漏传或传 nil 会导致 panic 或静默失败。
-
ctrl := gomock.NewController(t)必须在测试函数开头创建,并用defer ctrl.Finish()收尾 -
ctrl.Finish()不只是清理资源,它还会校验所有EXPECT()是否被满足,没调用或调用次数不对都会 fail 测试 - 同一个
ctrl可以创建多个 mock 实例,但不要跨测试函数复用
错误写法:
mockSvc := mocks.NewMockUserService(nil) // panic: nil controller
EXPECT() 预设行为时参数匹配很严格
EXPECT().GetUser(123) 这种写法只匹配「参数值完全等于 123」的调用,遇到 struct、slice、map 或指针会直接不匹配。
- 基本类型(int/string/bool)可用字面量直写
- 复杂类型必须用
gomock.Any()、gomock.Eq(x)、gomock.AssignableToTypeOf(x)等匹配器 - 返回值若含 error,建议显式写
Return(user, nil)而非Return(user, errors.New("xxx")),除非你真想测错误分支 - 如果方法有多个重载签名(比如接收
interface{}和具体类型),务必确认被测代码实际调用的是哪个
容易被忽略的一点:gomock 不支持部分参数匹配。比如 Do(ctx, req),你写了 EXPECT().Do(gomock.Any(), gomock.Eq(req)),但实际调用时 ctx 是另一个 context 实例,就会不匹配——此时该用 gomock.Any()。
gomock 不适合 mock 标准库函数或未导出方法
想 mocktime.Now、http.Get、os.ReadFile?gomock 完全无能为力,因为它只工作于 interface 层。
- 正确做法是把这类依赖抽象成接口或函数字段,例如定义
type Clock interface { Now() time.Time },然后注入 - 或者改用
gomonkey(需加-gcflags=all=-N -l关闭内联),但它在 macOS 10.15+ 权限受限,CI 上常失败 - 更轻量的方案是直接把函数定义为变量,如
var Now = time.Now,测试时替换为桩函数
真正难处理的是:你接手的旧代码里大量直调 log.Printf、rand.Intn 这类函数,又没法改接口——这时候别硬套 gomock,重构成本远低于强行绕过限制。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











