Service层单元测试需隔离data和transport依赖,仅验证业务逻辑;通过interface抽象依赖、gomock构造预设行为、覆盖正常/错误/边界场景。

单元测试怎么写 service 层不依赖 data 和 transport
service 层的单元测试必须隔离 data(数据库)、transport(HTTP/gRPC)和外部依赖,只验证业务逻辑是否按预期组装调用。Kratos 的分层设计(service → biz → data)天然支持这种隔离。
实操要点:
- 用 interface 抽象
data层依赖(如UserRepo),在service构造函数中传入,而非直接 new 或从全局容器取 - 测试时用
gomock或手写 mock 实现该 interface,返回预设数据或 error,比如mockRepo.GetUserFunc = func(ctx context.Context, id int64) (*User, error) { return &User{ID: id}, nil } - 避免在 test 中启动 gRPC server 或连接真实 DB;若用了
ent,别在 test 里调ent.NewClient(),mock 其Client.User.Query()方法即可 - 测试 case 要覆盖 happy path、error path(如 repo 返回
ent.ErrNotFound)、边界值(如空字符串、负 ID)
集成测试怎么跑通 HTTP API 端到端链路
集成测试目标是验证 api → service → biz → data 整条链路在真实 transport 下是否可通,但**不连生产 DB**——应使用内存 DB(如 sqlite)或临时测试 DB 实例。
常见错误现象:TestCreateUser 通过,但上线后 HTTP 400;或 panic 报 no transport registered。
实操建议:
- 在 test 文件中用
http.NewServer启一个真实 http.Server,传给kratos.New()启动最小 app,例如:srv := http.NewServer(http.Address("127.0.0.1:0"))(端口设为0让系统自动分配) - DB 初始化改用
sqlite.Open("file::memory:?cache=shared"),配合ent.Schema.Create(context.Background())建表,每次 test 用新 DB 实例 - 用
http.DefaultClient发起真实请求(如POST /v1/users),检查 status code、JSON body 字段、header(如X-Request-ID) - 别在集成测试里测中间件逻辑(如 authz)——那是 middleware 单元测试的事;这里只确认“请求能进、响应能出、数据落库”
怎么 mock 外部依赖比如 RabbitMQ 或 OpenFGA
只要你的代码没直接 new amqp.Connection 或硬编码 http.Post("http://openfga:8080/check"),就能安全 mock。关键在依赖注入点是否清晰。
容易踩的坑:
- RabbitMQ client 写成全局变量(如
var rabbitConn *amqp.Connection),导致 test 间状态污染、无法 reset;正确做法是把*amqp.Connection或封装后的Publisherinterface 作为参数传入 service - OpenFGA check 调用藏在 biz 层深处,且构造
user/object字符串靠拼接(如"user:" + userID),导致 mock 时难替换;应提取为独立函数并注入,比如func NewCheckClient(cfg OpenFGACfg) CheckClient - 忘记在 test teardown 中 close mock server 或 reset global state,后续 test 报
address already in use或缓存命中 - 用
httptest.NewServermock OpenFGA 时,handler 返回固定 JSON,但没设Content-Type: application/json,导致 SDK 解析失败静默返回 false
测试覆盖率怎么达标又不写无效 case
Kratos 项目默认不带测试覆盖率配置,go test -cover 只统计当前包;要全链路覆盖需明确范围,否则 api 层空 struct、errors 包里的常量都会拉低数字,失去意义。
推荐做法:
- 聚焦
biz和service包:它们含核心逻辑,go test -coverprofile=cover.out ./internal/biz/... ./internal/service/... - 忽略生成代码:proto 生成的
xxx.pb.go、xxx.http.pb.go不计入覆盖率(加-coverpkg排除) - 不要为 1:1 转发的 transport handler 写大量 case(如
GetUser只是调srv.GetUser()),这类逻辑已在 service 层覆盖 - 用
gocov或go tool cover生成 HTML 报告,重点看未执行的分支(如if err != nil分支是否被 error path 覆盖)
最易被忽略的是 error path 的传播:比如 data 层返回 sql.ErrNoRows,biz 层转成自定义 ErrUserNotFound,service 层映射为 gRPC status code,这一整条 error 转换链必须有对应测试,否则线上 500 会变成 200 空响应。











