Go中模拟依赖对象测试的核心是面向接口编程与依赖注入,通过手动构造轻量Mock结构体实现,无需反射式框架,强调清晰可控。
在 Go 中模拟依赖对象测试,核心是面向接口编程 + 依赖注入,而不是靠框架“自动 mock”。Go 没有反射式动态代理(如 Java 的 Mockito),所以 mock 是轻量、显式、手动构造的——这反而是优势:清晰、可控、无魔法。
mock 的前提是把外部依赖(数据库、HTTP 客户端、消息队列等)定义为接口。Go 的接口是隐式实现的,只要结构体实现了方法集,就满足该接口。
例如,不直接依赖 *sql.DB,而是定义:
type UserRepo interface {
GetUserByID(ctx context.Context, id int) (*User, error)
SaveUser(ctx context.Context, u *User) error
}
生产代码用 postgresRepo 实现它;测试时写个 mockUserRepo 结构体,仅实现需要测的方法。
不需要第三方库也能高效 mock。关键点:
type MockUserRepo struct {
GetCalls []int // 记录被调用的 id
GetUserResp *User
GetUserErr error
}
func (m *MockUserRepo) GetUserByID(_ context.Context, id int) (*User, error) {
m.GetCalls = append(m.GetCalls,
id)
return m.GetUserResp, m.GetUserErr
}
避免在业务逻辑里 new 具体实现,改用依赖注入:
type UserService struct {
repo UserRepo // 接口类型,非具体实现
}
func NewUserService(repo UserRepo) *UserService {
return &UserService{repo: repo}
}
// 测试中传入 mock
func TestUserService_GetProfile(t *testing.T) {
mockRepo := &MockUserRepo{
GetUserResp: &User{Name: "Alice"},
}
svc := NewUserService(mockRepo)
u, _ := svc.GetProfile(context.Background(), 123)
if u.Name != "Alice" {
t.Fatal("expected Alice")
}
if len(mockRepo.GetCalls) != 1 || mockRepo.GetCalls[0] != 123 {
t.Fatal("GetUserByID not called with expected ID")
}
}
简单场景手写 mock 更快更透明。复杂依赖或大量重复 mock 可考虑:
mockgen),适合大型项目统一风格database/sql,能验证 SQL 语句和参数,比手写 sql mock 稳定注意:不要为 mock 而 mock。如果一个函数只依赖纯函数或不可变数据,直接测逻辑即可;只有涉及 I/O、网络、时间、全局状态时,才需要隔离和模拟。
基本上就这些。Go 的 mock 不是炫技,而是用最少的代码守住边界、让测试专注逻辑本身。