最稳妥的方式是用 httptest.NewServer 启动内存假服务端并务必调用 .Close();或用自定义 RoundTripper 拦截请求验证结构;切勿误用 httptest.NewRequest 发请求。

怎么用 net/http/httptest 模拟 HTTP 服务端
测试 HTTP 客户端逻辑时,最稳妥的方式是不发真实请求,而是启动一个内存中的假服务端。Go 标准库的 httptest.NewServer 就是干这个的——它返回一个带真实 URL 的临时服务器,底层用 net/http.Server + 本地监听端口(或 Unix socket),但完全在进程内运行。
常见错误是直接传入 handler 函数却不处理 panic 或忽略关闭:
-
httptest.NewServer启动后必须调用.Close(),否则端口不释放,连续测试会报address already in use - handler 里如果 panic,
httptest不会透出堆栈,只返回 500;建议加recover或用httptest.NewUnstartedServer手动控制启动时机 - 若 handler 依赖外部状态(如数据库 mock),记得在每个测试用例前重置,避免污染
示例:
里面有2个文件夹。其中这个文件名是:finishing,是我项目还没有请求后台的数据的模拟写法。请求后台数据之后,瀑布流的js有一点点变化,放在文件名是:finished。变化在于需要穿参数到后台,和填充的内容都用后台的数据填充。看自己项目需求来。由于chrome模拟器是不允许读取本地文件json的,所以如果你要进行测试,在hbuilder打开项目就可以看到效果啦,或者是火狐浏览器。
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/user" && r.Method == "GET" {
w.WriteHeader(200)
json.NewEncoder(w).Encode(map[string]string{"id": "123"})
}
}))
defer ts.Close() // 关键
resp, _ := http.Get(ts.URL + "/api/user")
怎么用 http.Client + RoundTrip 拦截请求(不启服务)
有些场景不需要完整服务端,只想验证客户端是否构造了正确的 URL、Header、Body。这时可绕过网络层,把 http.Client 的传输层替换成自定义的 http.RoundTripper。
立即学习“go语言免费学习笔记(深入)”;
标准做法是实现 RoundTrip(*http.Request) (*http.Response, error) 方法,返回预设响应,完全跳过 socket 连接:
- 适合单元测试中校验请求结构,比如确认
Authorizationheader 是否带上 Bearer token - 注意:返回的
*http.Response必须设置Body字段(哪怕为空),否则client.Do会 panic - 别忘了设置
StatusCode和Header,否则默认为 0 / nil,下游逻辑可能意外跳过
示例:
var capturedReq *http.Request
rt := RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
capturedReq = req
return &http.Response{
StatusCode: 200,
Body: io.NopCloser(strings.NewReader(`{"ok":true}`)),
Header: make(http.Header),
}, nil
})
client := &http.Client{Transport: rt}
client.Get("https://example.com/test")
// 然后断言 capturedReq.URL.Path、capturedReq.Header 等
为什么 httptest.NewRequest 不能直接发请求
httptest.NewRequest 只生成一个 *http.Request 实例,它没有网络能力,也不触发任何 HTTP 协议行为。它存在的唯一目的是配合服务端 handler 测试(比如传给 yourHandler.ServeHTTP),而不是替代 http.Get 或 client.Do。
新手常犯的错是这样写:
req := httptest.NewRequest("GET", "https://api.example.com", nil)
// 然后以为 req 能自己发出请求 —— 实际上它只是个数据结构
真正该怎么做:
- 想测 handler 行为 → 用
httptest.NewRequest+httptest.NewRecorder,再调用handler.ServeHTTP(rec, req) - 想测 client 行为 → 用上面两种方式(mock server 或 mock transport),而不是伪造 request
- 若要复用 request 构造逻辑(比如加 header、set body),封装成函数返回
*http.Request,再交给真实 client 发送
第三方库如 gock 适合什么场景
gock 是基于 http.RoundTripper 的高级封装,主打“声明式拦截”:你告诉它“当匹配某 URL 和 method 时,返回这个响应”,它自动注册到全局 transport。
它省事,但也带来隐性成本:
- 默认作用于
http.DefaultClient,容易和其它测试冲突;务必在TestXxx开头调用gock.EnableNetworking()或gock.DisableNetworking()控制开关 - 匹配规则(如 query 参数、body 内容)写错会导致 mock 失效,而错误表现是“真实请求发出去了”,超时或连不上才暴露问题
- 不推荐在 Benchmark 或并行测试(
t.Parallel())中使用,因为它的 registry 是包级全局变量
简单场景下,原生 httptest 或自定义 RoundTripper 更可控;只有当需要快速模拟多个外部 API(且它们有固定 pattern)时,gock 才值得引入。
resp.Body.Close() 或至少 io.Copy(io.Discard, resp.Body),否则连接不会复用,大量测试跑完可能出现 too many open files。









