httpmock易失败因需在Transport层拦截且依赖全局DefaultClient;必须Activate/Deactivate配对、匹配完整URL、手动设header、用ResponderFunc处理body、每次返回新io.ReadCloser。

为什么 httpmock 在 Go 测试里容易“ mock 失败”?
因为 Go 的 http.DefaultClient 和标准库的底层 HTTP 传输机制是全局且不可替换的——你直接改 http.Client 字段没用,mock 必须在 Transport 层拦截请求。很多同学写完 httpmock.Activate() 就以为万事大吉,结果测试仍走真实网络,根本没进 mock 回调。
-
httpmock.Activate()必须在测试开始前调用,且不能漏掉defer httpmock.Deactivate()(否则后续测试可能被污染) - 所有被测代码必须使用
http.DefaultClient或显式传入未修改过的*http.Client;如果自己 new 了一个 client 并替换了Transport,httpmock压根看不到它 - mock 规则匹配的是完整 URL(含 scheme、host、path),
http://api.example.com/v1/users和https://api.example.com/v1/users是两条不同规则
怎么写一个可靠的 GET 请求 mock?
别只 stub 状态码,真实接口返回的 Content-Type、header、body 结构都会影响被测逻辑解析。比如 JSON 解析失败、空 body 被当成错误等,都是常见断点。
- 用
httpmock.RegisterResponder("GET", "https://api.example.com/users", httpmock.NewStringResponder(200, `{"id":1,"name":"alice"}`))注册响应 - 手动设置 header:
httpmock.NewStringResponder(200, `...`).Header().Set("Content-Type", "application/json") - 如果被测代码依赖重定向,记得用
httpmock.RegisterResponder("GET", "...", httpmock.NewErrorResponder(errors.New("mock redirect")))模拟失败路径
POST 请求 mock 时,Body 匹配为什么总不生效?
httpmock 默认不解析 request body,只做字符串全量匹配。而 Go 的 json.Marshal 输出字段顺序不定、空格/换行不一致,导致看似一样的 JSON 实际字节不同。
- 优先用
httpmock.RegisterResponderFunc手动判断 body:httpmock.RegisterResponderFunc("POST", "...", func(req *http.Request) (*http.Response, error) { ... }) - 在回调里用
ioutil.ReadAll(req.Body)(Go 1.16+ 改为io.ReadAll)读取原始 body 再解析或比对 - 避免直接用
httpmock.NewBytesResponder配合预生成 JSON 字节数组做精确匹配——除非你严格控制了序列化参数(如json.MarshalIndent+ 固定缩进)
测试跑完后 panic:「multiple goroutines reading same response body」?
这是 httpmock 返回的 mock response body 是单次可读的 io.ReadCloser,被测代码若多次调用 req.Body.Read 或重复 io.ReadAll,第二次就读不到内容,甚至 panic。
立即学习“go语言免费学习笔记(深入)”;
- 在 responder 函数里,每次返回新构造的
io.NopCloser(bytes.NewReader(...)),而不是复用同一个bytes.Reader - 如果被测逻辑确实需要多次读 body(比如先 sniff 再 parse),mock 侧要模拟这个行为:
bodyBytes := []byte(`{...}`); return &http.Response{Body: io.NopCloser(bytes.NewReader(bodyBytes))} - 注意:Go 1.19+ 中
http.Response.Body关闭后再次读会 panic,mock 也得遵守这个契约
最麻烦的不是写 mock,而是 mock 和真实 client 行为的细微差异——比如超时处理、重试逻辑、header 大小写敏感性、HTTP/2 流复用。只要被测代码碰到了这些点,mock 就得跟着补丁。所以别追求“全覆盖”,先 mock 掉你真正在意的那个失败分支。










