Go测试中不能起真实HTTP服务,因需保证快速、可重复、无外部依赖;真实服务易致端口冲突、goroutine泄漏、超时难控,且CI环境常禁外网;应通过mock RoundTripper绕过网络栈,精准控制请求响应。

Go 测试里为什么不能直接起真实 HTTP 服务
因为测试要快、可重复、不依赖外部状态。起真实 http.Server 容易端口冲突、泄漏 goroutine、超时难控,CI 环境还可能禁外网。Mock 的核心不是“假装有服务”,而是“绕过网络栈,让 http.Client 发出去的请求立刻拿到预设响应”。
- 真实服务启动慢,
net.Listen可能失败,server.Close()忘关会导致后续测试失败 - 一旦测试中调用
http.Get("https://api.example.com"),就已脱离可控范围 - 多数 HTTP client 库(包括标准库)都支持传入自定义
http.Transport,这才是 Mock 入口
用 httpmock 库拦截请求比手写 RoundTripper 更稳
httpmock 不是“启动 mock server”,而是注册一个假的 RoundTripper,在请求发出前就截住它。它对标准库透明,不需要改业务代码里的 http.Client 初始化逻辑——只要确保测试前启用、测试后清理即可。
- 安装:
go get -u github.com/jarcoal/httpmock - 启用必须在测试函数开头:
httpmock.Activate();否则所有请求照常走网络 - 必须在
defer httpmock.DeactivateAndReset(),否则会影响其他测试用例 - 匹配路径时注意:默认区分 query 参数顺序,
/api/users?id=1&name=a和/api/users?name=a&id=1是两个不同 pattern
func TestFetchUser(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpmock.RegisterResponder("GET", "https://api.example.com/user/123",
httpmock.NewStringResponder(200, `{"id":123,"name":"alice"}`))
resp, err := http.Get("https://api.example.com/user/123")
// ... 断言 resp.Body
}
遇到 Get https://...: dial tcp: lookup... 错误说明没激活或匹配失败
这个错误不是 DNS 问题,是 httpmock 没生效的明确信号。常见原因就三个:没调 httpmock.Activate()、注册的 URL 和实际发的 URL 字符串不完全一致、用了 HTTPS 但注册的是 HTTP(或反之)。
- 检查 URL 协议是否严格匹配:
https://和http://是不同 scheme,不能混用 - 路径末尾斜杠敏感:
/user≠/user/,建议统一用httpmock.RegisterRegexpResponder配正则 - 如果业务代码里用了自定义
http.Client并指定了Transport,那httpmock默认不接管——得手动把httpmock.GetTransport()塞进去 - 某些库(如
resty)底层也走http.Client,同样适用,但要注意其内部是否缓存了 client 实例
不用第三方库时,最简 RoundTripper Mock 写法
如果项目禁止引入新依赖,或者只想测一两个固定请求,直接实现 http.RoundTripper 接口更轻量。重点在于:不碰网络、不启 goroutine、不阻塞。
立即学习“go语言免费学习笔记(深入)”;
- 返回的
*http.Response必须带Body字段,且是io.ReadCloser类型,别直接传字符串 - 别忘了设置
ContentLength,否则某些 client 会卡在读 body - 不要在
RoundTrip方法里做耗时操作,比如解析 JSON 或 sleep——这会让测试变慢且不可靠
type mockTransport struct{}
func (m *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
body := ioutil.NopCloser(strings.NewReader(`{"ok":true}`))
return &http.Response{
StatusCode: 200,
Body: body,
Header: make(http.Header),
Request: req,
}, nil
}
// 使用:
client := &http.Client{Transport: &mockTransport{}}
HTTP Mock 的关键不在“像不像服务”,而在“能不能精准控制输入输出”。很多人卡在 URL 字符串拼写、协议头大小写、query 参数顺序这些细节上,而不是逻辑本身。多打两行日志输出实际请求的 req.URL.String(),比猜半天快得多。










