go没有内置mock框架,必须使用gomock等第三方库并基于interface设计;mockgen需指定含interface的文件,testify/mock因类型不安全已被淘汰。

mock 不是 Go 标准库的一部分,别直接 import "mock"
Go 没有内置 mock 框架,gomock、testify/mock、gomock(已归档)这些都不是语言自带的。你写 import "mock" 会报错:import "mock": cannot find package。必须显式引入第三方库,且要配合 interface 设计——这是 Go mock 能 work 的前提。
常见错误:先写死 struct 方法调用,再想“怎么 mock 这个 struct”,结果发现没法替换。Go 的 mock 依赖“依赖倒置”:函数/方法接收 interface 类型参数,运行时传入真实实现或 mock 实现。
- 定义业务逻辑时,只依赖
interface{ Do() error },不依赖具体类型 - 真实代码里传
&realService{},测试里传&mockService{} - 用
gomock生成 mock 类时,输入的是 interface 名(如go run github.com/golang/mock/mockgen -source=service.go),不是 struct
用 gomock 生成 mock 时,-source 必须指向含 interface 的 .go 文件
mockgen 不解析 import 链,它只读指定文件里的 type X interface{...}。如果 interface 定义在 types.go,但你执行 mockgen -source=main.go,即使 main.go import 了 types.go,也会报 no interfaces found。
正确做法:
- 把 interface 单独抽到
contract.go或interfaces.go - 运行
go run github.com/golang/mock/mockgen -source=contract.go -destination=mocks/mock_contract.go - 生成的文件需手动加
//go:build test注释(可选),避免被非测试构建包含
注意:生成的 mock 类名默认是 MockX,不是 XMock;方法名首字母大写,和原 interface 一致,比如原 interface 有 Fetch(ctx context.Context) ([]byte, error),mock 就有 EXPECT().Fetch().Return(...)。
testify/mock 不适合 Go,别被名字误导
testify/mock 是 testify v1 的遗留模块,v2+ 已移除,且它设计上依赖反射动态构造 mock,与 Go 的静态类型哲学冲突。它无法检查 method 签名是否匹配,容易在测试运行时 panic,例如:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
mock.On("Save", "id").Return(42) // 实际 Save 接收 *User,这里传 string → 运行时报 panic: mock: Unexpected Method Call
而 gomock 在编译期就校验签名:如果你 EXPECT().Save(&User{}) 但实际调用 Save("id"),go test 直接失败,提示 Unexpected call to *mocks.MockService.Save,错误位置清晰。
- 优先选
gomock:类型安全、IDE 可跳转、生成代码可读 - 小项目可手写 mock(实现 interface 的 struct + 字段控制返回值),省去工具链
- 避免
testify/mock和gock(HTTP mock)混用:前者 mock interface,后者 mock HTTP 请求,用途完全不同
mock.Expect().Method() 后必须调用 Return() 或 Times()
这是 gomock 最容易漏的点。写完 mockObj.EXPECT().Get("key") 不跟 .Return(...),测试运行时会 panic:mock: Unexpected Method Call,哪怕你没真调用该方法——因为 EXPECT 声明了“这个调用必须发生”,但没说返回什么。
典型场景:
- 期望调用一次并返回成功:
mockDB.EXPECT().QueryRow("SELECT...").Return(mockRow).Times(1) - 期望可能调用零次或多次:
mockLogger.EXPECT().Info(gomock.Any()).AnyTimes() - 需要验证参数内容:
mockSvc.EXPECT().Process(gomock.Eq(expectedInput)).Return(nil)
注意 Times(0) 是合法的,表示“绝不能调用”;但漏写 Return() 会导致编译通过、运行失败,且错误信息不提示“缺 Return”,只提示“调用未预期”,排查成本高。
真正难的不是生成 mock,而是把依赖拆成 interface;最难的也不是写 EXPECT,而是想清楚哪些交互必须验证、哪些只需存根(stub)。一旦 interface 定义模糊,mock 就变成缝合怪——看着跑过,其实没测到关键路径。










