必须用接口定义依赖才能 mock,因为go中mock的本质是提供另一满足相同契约的实现;若业务代码直接依赖具体类型(如*sql.db),测试时无法替换,只有依赖接口(如dbinterface)才可注入mock实现。

为什么必须用接口定义依赖才能 mock
Go 里没法对结构体或函数直接 mock,只能对 interface 做——因为 mock 的本质是「提供另一个满足同一契约的实现」。如果你的业务代码直接依赖 StripeClient 这种具体类型,测试时就无法替换;只有它依赖的是 PaymentService 接口,你才能塞进 MockPaymentService。
常见错误现象:cannot use *MockDB (type *MockDB) as type *sql.DB in argument——说明你试图把 mock 当成具体类型传,而不是接口。
- 接口方法要尽量窄:一个接口只含 2–3 个方法,避免
UserService里混着 DB、HTTP、Cache 逻辑 - 不要 mock 标准库类型(如
*http.Client),而是封装成接口再 mock - 生产代码中所有外部依赖入口点都应声明为接口字段,例如
db DBInterface而非db *sql.DB
手动 mock 比自动生成更可控的场景
小项目、核心逻辑简单、接口方法少于 4 个时,手写 mock 结构体比跑 mockgen 更快也更透明。你一眼就能看清它返回什么、是否记录调用、怎么触发 error。
示例中常用模式:
type MockUserRepo struct {
GetUserFunc func(id int) (*User, error)
SaveUserFunc func(*User) error
}
func (m *MockUserRepo) GetUser(id int) (*User, error) {
if m.GetUserFunc != nil {
return m.GetUserFunc(id)
}
return nil, errors.New("not stubbed")
}
- 字段用函数类型(
func(...))而非 map 或 switch,便于测试中直接赋值闭包 - 不初始化默认行为,强制测试显式设置期望路径,避免“看似通过实则没测”
- 如果需要验证调用次数或参数,加
CallCount int和LastID int字段,不用引入 testify/mock
testify/mock 在验证调用行为时容易踩的坑
testify/mock 的 On().Return() 看似方便,但实际调试时经常卡在「没命中预期」或「断言失败却不知哪次调用没被记录」。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
典型问题:
-
mock.On("GetUser", 1).Return(...)只匹配第一个参数严格等于1,传int32(1)就不匹配——Go 类型精确,别忽略类型后缀 - 没调用
mock.AssertExpectations(t),即使 mock 行为全错也不会报错 - 多个测试共用同一个 mock 实例,前一个测试残留的
On()影响后一个——每个测试应 new 一个干净实例 - 对返回 error 的 mock,写成
Return(nil, errors.New("x"))没问题,但若用Return(&MyError{}, nil),注意 error 接口判等逻辑
gomock 已弃用,改用 uber-go/mock 后的最小适配改动
原 github.com/golang/mock 已归档,现在推荐用 go.uber.org/mock。命令行基本兼容,但生成代码包路径和 import 要换。
关键变更点:
- 安装命令变为:
go install go.uber.org/mock/mockgen@latest - 生成时加
-mock_names避免命名冲突,例如:mockgen -source=user.go -destination=mocks/user_mock.go -package=mocks -mock_names=UserRepository=MockUserRepo - 生成的 mock 结构体不再带
EXPECT()方法链,而是用Ctrl+EXPECT字段访问期望配置 - 如果旧测试用
mock.Mock.On(...),新版本需改成mock.EXPECT().GetUser(gomock.Any()).Return(...)
真正麻烦的不是命令换,而是已有测试里大量 mock.AssertExpectations(t) 调用仍有效,但内部行为变了——比如并发调用时的期望匹配逻辑更严格,容易漏掉某次调用未声明。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










