gRPC测试应避免直连真实服务或mock接口,推荐用bufconn内存管道实现真实协议调用;服务端测试需启动完整gRPC栈,拦截器须正确传入NewServer,streaming测试需关注goroutine泄漏。

gRPC 客户端测试:别直接 dial 实际服务
本地跑单元测试时连真实 gRPC 服务,等于把测试变成集成测试——慢、不稳定、依赖外部状态。正确做法是用 grpc.Dial 的 mock 替代方案,或更推荐:用 bufconn 搭建内存管道,让 client 和 server 在同一进程里走真实 gRPC 协议,但不碰网络。
- 用
bufconn.Listen创建内存 listener,再启动一个真实的grpc.Server绑定它 - 客户端通过
grpc.WithContextDialer和bufconn.Dialer连这个内存地址,协议层完全一致 - 避免用第三方 mock 库(如
gomock生成的 client 接口)——它绕过序列化/压缩/拦截器,测不出编解码或 middleware 问题 - 注意:server 必须在 test setup 中显式
Start,并在defer里GracefulStop,否则可能 panic 或端口残留
gRPC 服务端测试:用 testservice.NewTestServiceServer 不够用
很多教程教你在 test 里 new 一个 service struct 然后直接调方法,这跳过了整个 gRPC 栈——没走 Unmarshal、没触发 UnaryInterceptor、没校验 metadata。真要测服务逻辑,得走完整调用链。
- 用
grpc.NewServer()启动最小 server,注册你的 service 实例 - 用
grpc.Dial(配合bufconn)拿到 client conn,再用生成的xxx.NewXxxClient发起真实 RPC 调用 - 如果 service 依赖外部组件(DB、cache),在 test 中注入 mock 实现,但保持 service struct 本身不变
- 别在 test 里改 service 的字段去“伪造状态”——容易漏掉初始化逻辑,应通过合法 RPC 请求驱动状态变化
grpc.Server 测试时拦截器不生效?检查 opts 传参顺序
拦截器(UnaryInterceptor / StreamInterceptor)必须在 grpc.NewServer 时作为 option 传入,且顺序影响行为:后注册的 unary interceptor 会包裹前一个。测试中常因 copy-paste 导致 interceptor 没被传进去,或者传成了空函数。
- 确认 test 中
grpc.NewServer(grpc.UnaryInterceptor(myInter))真实执行,不是写在注释里 - 拦截器内部 panic 会被 gRPC 捕获并转成
codes.Unknown错误,别只看 error == nil 就认为没进拦截器 - 若想验证拦截器是否调用,加一行
t.Log("interceptor hit")—— test 输出里能看到才说明它跑了 - 流式拦截器(
StreamInterceptor)比 unary 更易漏配,尤其当 service 同时提供 unary 和 stream 方法时
Mock gRPC 服务响应:用 testify/mock 生成 client interface 是下策
生成 client interface + mock 实现,看似快,但代价是彻底脱离 gRPC 运行时——你 mock 的是 Go 方法调用,不是网络请求。这意味着:无法测 deadline 超时、无法测 streaming 中断、无法测 TLS 握手失败、无法测 proto 默认值丢失等真实场景问题。
立即学习“go语言免费学习笔记(深入)”;
- 优先用
bufconn+ 真实 server,哪怕 server 只返回固定 response - 真要 mock client(比如测试重试逻辑),用
grpc.WithTransportCredentials(insecure.NewCredentials())配合本地未监听端口,触发连接拒绝错误,比 mock 方法更贴近现实 - 如果必须 mock,至少确保 mock 的 error 类型是
status.Error,而不是普通errors.New,否则上层status.Code(err)会 panic - mock 返回的 response struct,字段必须和 proto 生成的一致(包括零值字段),否则反序列化差异会导致测试通过但线上失败
最麻烦的其实是 streaming 接口测试——buffer 管理、context cancel、client-side close 时机,三者稍有错位就会死锁或 goroutine 泄漏。别指望一次写对,先用 runtime.NumGoroutine() 对比前后数量,再逐个 case 加日志。










