Kratos单元测试核心是隔离依赖、控制输入、验证行为,需用test.NewContext构造带值上下文、mock dao接口、httptest模拟请求,避免启动服务或mock框架底层。

Kratos 单元测试不靠启动完整服务,也不该 mock 框架底层(比如 kratos.New 或 app.Run),核心是隔离依赖、控制输入、验证行为。写错方向会导致测试慢、不稳定、一改就崩。
test.NewContext 构造带值的 context,别用 context.Background()
中间件或 service 里常从 ctx.Value 取用户 ID、traceID、租户信息等。直接用 context.Background() 会让代码 panic,比如 panic: interface conversion: interface {} is nil, not string。
- 用
test.NewContext(context.Background(), test.WithValue("user_id", int64(123)))注入测试所需上下文值 - 避免在
TestMain里调conf.Load()或log.Init()—— 它们会污染全局状态,导致并发测试失败 - 若 service 依赖
log.Logger,直接传log.NewHelper(log.NewNopLogger()),别绕路初始化 zap
service 层测试:new 实例 + mock 依赖,不碰 server
service 不该感知 HTTP/gRPC 协议细节,测试时也完全不需要启动任何 server。重点是验证业务逻辑是否按预期调用 dao、返回错误、组装响应。
- 为每个 dao 接口定义明确的 interface,例如
type UserRepo interface { GetUser(ctx context.Context, id int64) (*User, error) } - service 初始化函数接收该 interface,而非具体 struct:
func NewUserService(repo UserRepo) *UserService - 手写 mock 或用 gomock 生成实现,注意 error 类型必须严格一致(
error≠*errors.StatusError) - 别 mock
paladin.Toml—— 改用paladin.Toml(`{"timeout": 30}`)加载内存配置,更轻、更可控
handler 测试:用 httptest.NewRequest + httptest.NewRecorder,跳过路由中间件
handler 是协议适配层,测试目标是“给定请求,是否返回正确 status/code/body”,不是测整个 HTTP 栈。走真实路由链路会引入 middleware、validator、auth 等干扰项,让测试脆弱且难 debug。
- 构造请求:
r := httptest.NewRequest(http.MethodGet, "/users/123", nil) - 构造 recorder:
w := httptest.NewRecorder() - 直接调 handler 方法:
h.GetUser(w, r)(不是router.ServeHTTP) - 检查结果:
assert.Equal(t, http.StatusOK, w.Code),assert.JSONEq(t, `{"id":123}`, w.Body.String()) - 如果 handler 里用了
middleware.Auth,测试前手动塞值到 context:ctx := test.NewContext(r.Context(), test.WithValue("user_id", int64(123))),再r = r.WithContext(ctx)
gomock 生成和使用要注意签名一致性
gomock 生成的 mock 方法签名必须和 interface 完全一致,包括参数名、顺序、类型(尤其是 error)、是否指针。常见失败原因是:proto 生成的 error 类型是 *status.Error,而你 mock 的 interface 写成了 error,运行时 panic。
- 生成命令示例:
mockgen -source=user_repo.go -destination=mocks/mock_user_repo.go -package=mocks - mock 方法里别做耗时操作(如 sleep、DB 查询),mock 就是返回固定值或简单逻辑
- 不要在 mock 方法里调用
t.Fatal—— 它会终止 goroutine,导致测试框架无法统计失败数;改用return nil, errors.New("mock error") - 多个测试共用一个 mock 实例时,注意 state 是否被污染;建议每个 test case new 一个新 mock
最易被忽略的一点:Kratos 的 data 包里常直接 new *sql.DB 或 redis.Client,这种硬依赖根本没法单元测试。必须补 interface 抽象,哪怕只有一两个方法 —— 这不是过度设计,是单元测试能跑起来的前提。











