httptest.Server是首选方案,因其不走网络、启动快、生命周期易管理;关键需将srv.URL注入被测代码而非硬编码地址,并注意Host头校验、JSON字段导出规则及连接池清理。

直接用 httptest.Server 替换真实请求地址,是最可靠、最轻量的方案。别碰 gock 或自定义 RoundTripper,除非你明确需要拦截不可修改的遗留代码。
为什么 httptest.Server 是首选
它不走网络、不依赖端口复用、启动快、生命周期由 defer srv.Close() 自动管理。关键不是“怎么造响应”,而是“怎么让被测代码发请求时打到本地服务上”。
- 被测代码里不能硬编码
"https://api.example.com",必须把基础 URL 抽成字段或构造参数 -
srv.URL永远是http://开头,哪怕你模拟的是 HTTPS 接口——httptest.Server本身不支持 TLS,测试中也不需要 - 如果用了第三方 HTTP 客户端(如
resty),确保它支持设置BaseURL;优先走 URL 替换,而不是改 Transport
常见错误:请求没打到 mock 服务上
现象是测试超时、连接拒绝,或返回真实 API 的 404/500。根本原因几乎总是:没把 srv.URL 注入到实际发起请求的 client 或结构体里。
- 检查被测函数是否接收
*http.Client或类似可配置 client;若只用http.Get,必须改造成可注入形式 - 确认 handler 中路由匹配逻辑和测试请求路径完全一致,比如
r.URL.Path == "/users/123",不要靠字符串拼接判断 - 如果被测代码校验了
Host头,需在 handler 里手动设w.Header().Set("Host", "api.example.com"),或在请求中显式设置req.Host = "api.example.com"
JSON 响应体解析失败?先看 struct 字段导出规则
这不是 HTTP 层问题,而是 Go 的 JSON 序列化规则导致的静默失败:字段名首字母小写不会被 json.Marshal/json.Unmarshal 处理。
- mock 响应中写
{"id": 123},但被测 struct 是type User { id int }→id字段永远为零值 - 正确写法是
type User { ID int `json:"id"` },或直接大写字段名ID int - 建议在测试里加
t.Log(string(body))打印实际返回内容,别靠猜
别忘了清理连接池和 server
httptest.Server.Close() 只关服务,不清理复用的 http.Client 连接池。如果测试中复用了同一个 client,它的连接可能还缓存着旧 server 的地址,下次请求会卡住或报 connection refused。
- 每个测试用例新建独立
http.Client,或显式调用client.Transport.(*http.Transport).CloseIdleConnections() - 务必
defer srv.Close(),否则多次运行测试会因端口占用失败 - 不用
httpmock或gock就不用操心Activate/Deactivate,这是它们最易漏的点
真正难的不是写 mock,而是让被测代码具备可替换性——URL、client、接口实现,都得提前设计成可注入的。一旦结构松动,后续所有测试都会变得脆弱。


















