gomock仅用于接口模拟,需先定义导出接口、依赖注入并显式调用ctrl.finish()校验;mockgen失败主因是路径错误、包名不匹配或接口未导出,必须在模块根目录运行并指定-source、-destination和-package。

gomock 不是用来“集成”的框架,它不 runtime 注入、不自动扫描、不 hook 函数 —— 它只做一件事:为接口生成可断言行为的 mock 实现。你得先有接口、依赖注入、测试控制权,gomock 才能起作用。
为什么 mockgen 生成的代码编译不过?常见路径和包名陷阱
mockgen 对输入路径极其敏感,稍错就生成空文件或报错 no interfaces found:
-
mockgen -source=internal/repo/user.go:要求该文件里必须有 export 接口(首字母大写),且不能在test后缀文件里定义 -
mockgen myapp/internal/repo UserRepo:要求myapp/internal/repo是 go module 下可 import 的合法包路径,且UserRepo是其中定义的接口名(大小写、拼写必须完全一致) - 如果接口在
pkg/user/user.go,但当前工作目录不在 module 根,go list找不到包,mockgen就会静默失败
建议做法:
- 始终在 module 根目录下运行
mockgen - 用
mockgen -source=... -destination=mocks/mock_user.go -package=mocks显式指定输出,避免覆盖源码 - 生成后立刻
go build mocks/...验证是否可编译
gomock.NewController(t) 必须 defer ctrl.Finish(),否则 panic
gomock.Controller 不是装饰器,它是状态机控制器。它记录所有 EXPECT() 声明,并在 Finish() 时校验是否全部被满足。漏掉 defer ctrl.Finish(),测试可能通过但实际没验证任何调用;更糟的是,若 mock 方法被多调用而没设 .Times(n),Finish() 会 panic 并提示 “expected call at … never happened”。
典型错误写法:
ctrl := gomock.NewController(t) mockRepo := mocks.NewMockUserRepo(ctrl) // 忘了 defer ctrl.Finish()
正确写法(必须):
ctrl := gomock.NewController(t) defer ctrl.Finish() // 这行不能少,也不能挪到函数末尾以外的位置 mockRepo := mocks.NewMockUserRepo(ctrl)
注意:ctrl.Finish() 会检查所有 EXPECT 是否被满足 —— 包括未设置 .Return() 的方法调用也会触发失败。
EXPECT().Method() 返回值必须与真实接口签名严格一致
gomock 生成的 EXPECT() 方法返回的是链式 builder,但它的 .Return(...) 参数顺序、类型、数量,必须和接口中对应方法的返回列表逐位匹配。Go 不支持“忽略某个返回值”,哪怕你想 mock 只返回 error,也得写全:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
假设接口是:
type UserRepo interface {
Get(ctx context.Context, id int) (*User, error)
}
那么以下写法是错的:
mockRepo.EXPECT().Get(mock.Anything, 123).Return(nil) // ❌ 缺少 error
mockRepo.EXPECT().Get(mock.Anything, 123).Return(errors.New("not found")) // ❌ 类型错:*User 不能是 error
正确写法(两种常见情形):
- 成功路径:
mockRepo.EXPECT().Get(mock.Anything, 123).Return(&User{ID: 123}, nil) - 错误路径:
mockRepo.EXPECT().Get(mock.Anything, 123).Return(nil, errors.New("not found"))
特别注意:mock.Anything 是通配符,但它不跳过参数校验 —— 方法仍会被调用,只是不比对值。若你传了 nil 而接口要求 context.Context,运行时 panic。
别 mock 标准库类型,先封装成接口
gomock 只能 mock 接口,而 <em>http.Client</em>、sql.DB、time.Now 都不是接口。直接对它们写 mock 会失败,或导致测试脆弱。
正确姿势是提前抽象:
- 网络请求 → 定义
type HTTPDoer interface { Do(*http.Request) (*http.Response, error) },让生产代码依赖它,再 mock 这个接口 - 时间 → 定义
type Clock interface { Now() time.Time },把time.Now包进结构体,测试时替换为固定时间 - 数据库 → 不 mock
*sql.DB,而是定义type Querier interface { QueryRowContext(...),或直接用go-sqlmock(它自己拦截底层 driver,不走接口 mock)
最常被忽略的一点:这些封装接口要**尽早出现在业务逻辑构造阶段**,而不是在方法内部临时创建。否则无法注入 mock。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










