Go单元测试中mock的核心目标是隔离被测代码与外部依赖,通过接口抽象、手写Mock或mockery工具生成Mock,结合依赖注入实现干净的依赖替换与行为验证。

在 Go 中做单元测试时,模拟依赖接口(mock)的核心目标是隔离被测代码与外部依赖,比如数据库、HTTP 客户端、消息队列等。Go 本身没有内置 mock 框架,但依靠接口抽象 + 工具生成或手写 mock,就能干净地实现依赖替换。
用接口定义行为,而非具体实现
Go 的 mock 能力源于其接口的隐式实现机制。只要结构体实现了接口的所有方法,它就满足该接口类型。因此第一步是把依赖抽象成小而专注的接口,而不是直接依赖 struct 或第三方库类型。
- 避免直接使用
*sql.DB或http.Client—— 它们方法多、耦合重、难 mock - 定义精简接口,只包含当前业务需要的方法。例如:
type UserRepository interface {
GetByID(ctx context.Context, id int) (*User, error)
Save(ctx context.Context, u *User) error
}
这样,你的 service 层只依赖 UserRepository,后续可自由替换为真实实现或 mock 实现。
手写 mock:轻量、可控、无额外依赖
对简单场景,直接实现接口即可。mock 结构体通常包含预设返回值或行为控制字段(如计数器、错误开关),便于测试不同分支。
立即学习“go语言免费学习笔记(深入)”;
- 给 mock 添加字段控制返回结果,比如
ReturnError bool或Users map[int]*User - 方法内不调用外部资源,只按规则返回数据或错误
- 可加导出字段(如
CalledTimes int)验证是否被调用、调用几次
type MockUserRepo struct {
Users map[int]*User
ReturnError bool
CalledTimes int
}
func (m *MockUserRepo) GetByID(_ context.Context, id int) (*User, error) {
m.CalledTimes++
if m.ReturnError {
return nil, errors.New("db timeout")
}
return m.Users[id], nil
}
用 mockery 自动生成 mock(推荐中大型项目)
mockery 是 Go 社区主流的 mock 代码生成工具,能基于接口自动生成符合标准的 mock 结构体和方法。
- 安装:
go install github.com/vektra/mockery/v2@latest - 为某个接口生成 mock:
mockery --name=UserRepository --output=mocks/ - 生成的 mock 支持链式调用(如
mock.On("GetByID", mock.Anything, 123).Return(...)),也支持断言调用次数、参数匹配等 - 配合
testify/mock使用可增强断言能力,但纯 mockery 生成的 mock 已足够用于大多数单元测试
在测试中注入 mock,完成隔离
Go 推崇依赖注入(DI)而非全局单例。测试时,将 mock 实例传入被测对象(如 service 构造函数或方法参数),即可彻底切断对外部系统的依赖。
- 不要在 service 内部 new 出 DB 或 client;通过构造函数接收依赖
- 测试中创建 mock,构造 service,再调用待测方法
- 验证返回值、错误、mock 的调用状态(如是否调用了 Save、参数是否正确)
func TestUserService_GetUser(t *testing.T) {
mockRepo := &MockUserRepo{
Users: map[int]*User{1: {ID: 1, Name: "Alice"}},
}
svc := NewUserService(mockRepo)
user, err := svc.GetUser(context.Background(), 1)
require.NoError(t, err)
require.Equal(t, "Alice", user.Name)
require.Equal(t, 1, mockRepo.CalledTimes)
}










