用gomock模拟接口异常需先基于真实interface生成mock文件,再通过EXPECT()明确设置错误返回;httptest.Server可构造不稳定HTTP服务,但需手动在handler中添加随机失败或延迟逻辑。

用 gomock 模拟微服务依赖的接口异常
微服务测试中,你没法总等真实下游服务出错才验证上游容错逻辑。直接 mock 依赖接口,是最可控的方式。gomock 是 Go 官方推荐的 mock 工具,但容易卡在「生成 mock 接口」这步。
常见错误是:只 mock 了结构体方法,没 mock 接口定义;或者 mock 后没调用 EXPECT() 设置返回值,导致测试运行时 panic 报 Unexpected call to ...。
- 先用
mockgen基于真实 interface 生成 mock 文件,比如下游服务定义了UserService接口,就执行:mockgen -source=user_service.go -destination=mocks/mock_user_service.go -package=mocks
- 在测试中创建 mock controller 和 mock 实例:
ctrl := gomock.NewController(t)<br>defer ctrl.Finish()<br>mockSvc := mocks.NewMockUserService(ctrl)
- 明确指定异常路径:比如模拟网络超时,就写
mockSvc.EXPECT().GetUser(gomock.Any()).Return(nil, errors.New("i/o timeout"));不要只写.Return(nil, nil),否则掩盖了错误处理逻辑
用 httptest.Server 构造不稳定的 HTTP 依赖服务
有些场景下,mock 太“干净”,无法复现连接中断、响应延迟、503 等真实网络异常。这时需要一个轻量、可编程的 fake server,httptest.Server 就是为此设计的。
注意它默认不支持随机失败或动态延迟——必须手动在 handler 里加逻辑,否则所有请求都成功,起不到压测或容错验证作用。
立即学习“go语言免费学习笔记(深入)”;
- 构造一个会概率性返回 503 的 server:
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {<br> if rand.Float64() < 0.2 { // 20% 概率<br> w.WriteHeader(http.StatusServiceUnavailable)<br> return<br> }<br> json.NewEncoder(w).Encode(map[string]string{"id": "123"})<br}))<br>defer server.Close() - 把 client 的
http.Client.Timeout设小(比如 100ms),才能触发超时分支;否则即使 server sleep 200ms,测试也不会报超时错误 - 别忘了在测试前重置
http.DefaultClient或传入自定义 client,否则可能被其他测试污染
在测试中触发熔断器(如 gobreaker)状态切换
光让下游返回错误还不够——得确认你的熔断逻辑真被触发了。很多同学写了 gobreaker,但测试里没等状态变化就断言,结果永远看到 State: closed。
熔断器有状态跃迁窗口,不是“一错就开”,而是按失败率+时间窗口统计后才切换。直接查状态容易误判。
- 设置低阈值便于测试:比如
Settings: gobreaker.Settings{ReadyToTrip: func(count uint64, consecutiveFailed uint64) bool { return consecutiveFailed >= 3 }} - 连续调用 3 次失败接口(用上面的
httptest.Server或 mock),再 sleep 至少Settings.SleepWindow(默认 60s,测试时建议设成 100ms) - 用
cb.State()断言是否为gobreaker.StateOpen,而不是检查 error 是否非空——因为 open 状态下仍会返回 fallback 错误,但那不是原始错误
避免测试间状态污染:每个测试用独立的 gobreaker.CircuitBreaker 实例
多个测试共用同一个熔断器实例,会导致失败计数累积、状态残留,出现“上一个测试失败影响下一个测试通过”的诡异现象。
尤其当你用全局变量或 init 函数初始化 gobreaker,又没在测试前后清理,问题会更隐蔽。
- 在每个 test 函数内 new 一个新实例:
cb := gobreaker.NewCircuitBreaker(gobreaker.Settings{...}) - 不要在
init()或包级变量中初始化熔断器;如果业务代码强依赖单例,就在测试时用依赖注入替换掉它 - 若使用
go.uber.org/fx等 DI 框架,测试时用fx.NopLogger+fx.Supply注入定制化gobreaker.CircuitBreaker,而非复用生产配置


















