go单元测试依赖注入靠接口+构造函数参数+手动组装,newuserservice(repo userrepository)即满足本质:参数为接口且测试可传任意实现,无需di框架;错误在于硬编码全局依赖或接收具体类型。

Go 单元测试里依赖注入不是靠框架,而是靠接口 + 构造函数参数 + 手动组装——框架(如 testify/mock)只是可选辅助,不是必需品。
为什么 NewUserService(repo UserRepository) 就是依赖注入
只要 NewUserService 的参数是接口类型(如 UserRepository),且你在测试时能传入任意实现(比如 *fakeUserRepo 或 *mockUserRepo),就满足依赖注入的本质。不需要任何“DI 框架”参与。
常见错误现象:测试时发现 repo.GetUser panic,一查是 NewUserService 里直接用了全局 db *sql.DB,没走参数传入;或者构造函数接收的是 *sql.DB 而非接口,导致无法替换。
- 每个服务的构造函数必须只依赖接口,不依赖具体类型
- 接口定义要放在调用方可见的包里(比如
service包定义UserRepository,repo包实现它) - 测试文件中直接
import该接口,才能实现或 mock
手写 fake 实现比引入 testify/mock 更快更稳
多数场景下,一个结构体 + 几个方法就能覆盖测试需求,比生成 mock、写期望、校验调用次数更轻量、边界更清晰。
使用场景:验证业务逻辑是否在用户不存在时返回特定错误,或是否正确拼接了 SQL 参数。
- fake 结构体字段用于记录调用行为,例如
Calls []string或LastQuery string - 方法只返回预设值或错误,不触发真实 IO:
return nil, errors.New("timeout") - 避免在 fake 里 sleep、select、http.Do —— 否则测试会变慢、不稳定
- 测试中直接传
&fakeRepo{users: map[int]*User{1: {Name: "a"}}}给NewUserService
testify/mock 什么情况下值得用
当你需要严格验证调用次数、参数顺序、或多个方法间状态流转时,mock 工具才有不可替代性。但代价是配置复杂、错误信息难读、且容易因签名变更而大面积报错。
常见错误现象:mock: Unexpected call to *mocks.MockUserRepository.GetUser,但你其实只想测返回值,不关心调用几次。
- 仅当接口方法多、调用路径深、且需断言“某方法被调用且参数为 X”时才引入
- 用
gomock生成 mock 后,别改接口签名,否则生成代码失效 -
testify/mock不支持泛型接口,遇到Repository[T]类型得手动写 fake - mock 对象本身也是依赖,测试中仍要通过构造函数注入,没绕过 DI 逻辑
main 是唯一合法的依赖组装点,测试不能绕过它
所有依赖必须在 main() 中自底向上创建并传递:先 db := sql.Open(...),再 repo := NewUserRepo(db),最后 svc := NewUserService(repo)。测试时也应模拟这一链条,而不是在 test 文件里零散 new。
容易被忽略的地方:很多人在测试中直接 svc := &UserService{repo: &fakeRepo{}},绕过了构造函数里的 nil 校验和字段初始化逻辑,导致线上正常、测试漏掉空指针问题。
- 测试中优先调用
NewUserService,而非字面量赋值结构体 - 构造函数里加
if repo == nil { panic("repo is required") },测试能提前暴露漏传 - 如果
NewUserService还接收logger *zap.Logger,测试时传zap.NewNop()即可,不用 mock 日志
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











