gomock 必须用 mockgen 生成 mock 接口文件,不能手写;每个测试需调用 defer ctrl.Finish() 校验期望,且 EXPECT() 必须在被测代码调用前设置。

gomock 生成 mock 接口文件必须用 mockgen,不能手写
gomock 不提供运行时动态 mock,所有 mock 类型都得提前生成。你定义的接口(比如 UserService)必须先用 mockgen 工具生成对应 mock 结构体,否则编译直接报错:类型未定义或无法实现接口。
常见错误是以为 `go test` 能自动补全 mock,结果跑测试时提示 undefined: mocks.MockUserService —— 这说明没生成,不是 import 错了。
- 确保已安装:
go install github.com/golang/mock/mockgen@latest - 生成命令示例(接口在
pkg/user/user.go中):mockgen -source=pkg/user/user.go -destination=mocks/mock_user.go -package=mocks - 生成后记得
go mod tidy,避免因新文件未被识别导致 import 失败
mock 对象生命周期由 gomock.Controller 管理,漏调 Finish() 会 panic
每个测试用例里,gomock.NewController(t) 创建的 controller 必须配对调用 ctrl.Finish(),否则测试结束时 gomock 会检查预期调用是否全部满足,未满足就 panic 并打印类似 Unexpected call to ... 的错误,且不显示具体哪行没覆盖到。
这不是可选习惯,是强制契约:controller 持有所有 mock 调用记录,Finish() 是它做断言的唯一时机。
立即学习“go语言免费学习笔记(深入)”;
- 必须在
defer ctrl.Finish()—— 放在函数开头最安全,避免分支遗漏 - 不要复用 controller 跨测试函数,每个
TestXxx独立 new + finish - 如果测试中提前 return(比如 error 分支),没 defer 就必然 panic
EXPECT() 设置行为必须在调用前完成,顺序敏感
gomock 是“录制-回放”模型,mockObj.EXPECT().Method(...).Return(...) 是录制预期;真实调用发生时才匹配。如果先调用了方法,再写 EXPECT(),测试会失败并报 Unexpected call to ...,因为 controller 根本没录这条预期。
典型翻车场景:在 setup 阶段初始化 mock,但把 EXPECT() 写在 assert 或执行逻辑之后。
- 务必把所有
EXPECT()放在被测函数调用之前 - 多次调用同一方法?用
Times(n)显式声明,别依赖默认 1 次 - 参数匹配要小心:
gomock.Any()可通配,但nil、空 struct、未导出字段可能不匹配,建议用gomock.Eq(expectedVal)
接口设计影响 mock 可测性,避免返回 concrete type 或带副作用
gomock 只能 mock 接口,不能 mock struct 或函数。如果你的 service 方法返回 *sql.DB 或 http.Client,就无法用 gomock 替换——得先抽象成接口(如 DBExecutor、HTTPDoer),再让生产代码和测试代码都依赖该接口。
另一个坑是接口方法带副作用(比如直接写文件、发 HTTP),这会让测试变慢、不稳定。gomock 本身不解决这个问题,但它倒逼你把副作用隔离进小接口,方便替换成内存实现或 error 注入。
- 检查你的接口是否含
error返回以外的 side effect;如果有,拆出来单独接口 - 避免接口方法参数含匿名函数或
context.Context以外的闭包,mockgen 可能无法正确解析签名 - 如果接口方法太多,考虑按职责拆分(如
UserReader/UserWriter),降低单个 mock 的复杂度


















