Kratos 测试需抽象客户端依赖:gRPC 应 mock 自动生成的 xxxClient 接口,HTTP 应封装 httpx.Client 为接口并用 httpmock 拦截;必须避免直接 mock grpc.Dial 或替换 http.DefaultClient,且每个测试需独立初始化和清理 mock。

Mock Kratos 的 grpc.ClientConn 和 http.Client
Kratos 默认使用 grpc.ClientConn(gRPC 客户端)和封装了 http.Client 的 httpx.Client(HTTP 客户端),它们都依赖底层网络连接。离线测试时必须拦截这些调用,否则测试会失败或超时。
推荐方式是用 Go 原生 gomock + mockgen 生成接口 Mock,或更轻量的 testify/mock,但注意:Kratos 的客户端本身不是接口,不能直接 mock —— 必须先提取依赖接口。
- 对 gRPC:定义服务接口(如
UserServiceClient),用mockgen生成实现,测试中注入该 mock 实例 - 对 HTTP:将
httpx.Client封装为接口(如HTTPDoer),测试时传入httpmock或自定义RoundTripper拦截请求 - 避免直接 mock
grpc.Dial或替换全局http.DefaultClient,这会导致测试污染和并发问题
用 httpmock 拦截 httpx.Client 发起的 HTTP 请求
Kratos 的 httpx.Client 底层仍走标准 http.RoundTripper,所以 httpmock 完全可用,且比重写整个 client 更干净。
关键点在于:必须在测试初始化时启用 httpmock.Activate(),并在 defer httpmock.DeactivateAndReset() 清理,否则后续测试可能被干扰。
- 注册响应:
httpmock.RegisterResponder("GET", "https://api.example.com/users/123", httpmock.NewStringResponder(200, `{"id":123,"name":"mock"}`)) - 确保
httpx.Client使用的是默认 transport(即未显式设置httpx.WithTransport),否则需手动把httpmock.Transport注入进去 - 如果业务代码用了
httpx.NewClient(...)并复用了 client 实例,记得在每个 test case 中重置httpmock状态,防止匹配残留
gRPC 接口 Mock 必须基于服务定义的 Client 接口
Kratos 的 gRPC 客户端类型(如 usersv1.NewUserClient 返回的)本质是结构体,无法直接 mock。正确做法是:从 *.proto 生成的 xxxClient 接口入手(它已由 protoc-gen-go 自动生成),然后 mock 这个接口。
例如,usersv1.UserClient 是接口,可被 mockgen 处理:
mockgen -source=third_party/api/user/v1/user.pb.go -destination=mocks/user_client_mock.go -package=mocks
生成后,在测试中构造 mock 实例并注入到待测 service 中:
- 用
gomock.Ctrl控制生命周期,EXPECT()声明期望调用和返回值 - 若方法带
context.Context参数,mock 时需保留该参数(不能省略),否则签名不匹配 - 注意 streaming 方法(如
UserClient.ListUsers)需 mock 返回的UserService_ListUsersClient接口,比 unary 更复杂,建议优先覆盖核心 unary 场景
DI 容器里替换真实 client 时要注意生命周期和作用域
Kratos 使用 di.Container 管理依赖,测试中常通过 app.New(...).WithContainer(...) 替换依赖。但容易忽略两点:一是 mock 对象是否被多 goroutine 共享,二是 container 是否在每个 test case 中重建。
- 不要在
TestMain中全局注册 mock client,应每个TestXxx函数内新建 container 并注入新 mock 实例 - 如果被测 service 本身缓存了 client(比如字段赋值),需确保每次测试前重新构造 service 实例,而不是复用
- 使用
kratos.Mock辅助函数(如kratos.Mock(&client, mockClient))仅适用于部分场景,它本质是反射赋值,不推荐用于生产级测试逻辑
真正难的不是 mock 本身,而是让所有依赖路径都走你可控的接口抽象 —— 如果某处硬编码了 grpc.Dial 或 new 出了未抽象的 httpx.Client,那 mock 就失效了。


















