httptest.NewRequest用于构造内存请求测试handler,需设method、URL、body及Content-Type;NewRecorder捕获响应供断言;路由参数须手动注入context;客户端测试应使用NewUnstartedServer而非NewRecorder。

Go测试中如何用httptest.NewRequest构造请求
直接调用http.HandlerFunc或http.Handler时,不能传入真实网络请求,必须用httptest.NewRequest生成可修改的*http.Request。它不触发DNS、连接或TLS,纯内存操作,安全可靠。
常见错误是忽略body类型和Content-Type头不匹配,导致r.ParseForm()或json.NewDecoder(r.Body).Decode()失败。
-
method和url必须非空,否则NewRequestpanic - POST/PUT请求若带JSON数据,需显式设置
Content-Type: application/json - 表单数据要用
strings.NewReader("key=value&other=1")并设application/x-www-form-urlencoded - 路径含查询参数时,推荐用
url.Parse拼接,避免手动编码出错
req := httptest.NewRequest("POST", "/api/users", strings.NewReader(`{"name":"alice"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer abc123")
如何用httptest.NewRecorder捕获响应
httptest.NewRecorder()返回一个实现了http.ResponseWriter接口的记录器,所有写入(状态码、header、body)都会被缓存,不发送到网络。这是验证 handler 行为的核心工具。
注意:recorder.Body.String()只能在 handler 执行完后调用;提前读会得到空字符串。另外recorder.Code是 int 类型,不是字符串,别误用strconv.Itoa转换后再比较。
- 响应体是
*bytes.Buffer,支持Bytes()、String()、Len() -
recorder.Header()返回的是http.Header副本,修改它不影响已写入的响应头 - 若 handler 调用了
http.Error或panic,仍可通过recorder.Code和recorder.Body断言结果
rr := httptest.NewRecorder()
handler := http.HandlerFunc(yourHandler)
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusCreated {
t.Errorf("expected status %d, got %d", http.StatusCreated, rr.Code)
}
if !strings.Contains(rr.Body.String(), `"name":"alice"`) {
t.Errorf("response body doesn't contain expected JSON")
}
测试带路由参数的HTTP handler(如gorilla/mux或chi)
第三方路由器(如gorilla/mux)依赖request.Context()中的变量,单纯用httptest.NewRequest无法自动注入URLVars。必须手动把变量塞进context,再赋给req。
漏掉这步会导致mux.Vars(r)返回空 map,handler 读不到:id等参数,逻辑直接跳过或 panic。
- 使用
chi.URLParam时同理,需用chi.WithValue包装req - 不要试图修改
req.URL.Path来“欺骗”路由——中间件和路由匹配早已完成 - 如果 handler 内部调用
r.Context().Value(),也要提前注入对应 key
// gorilla/mux 示例
r := mux.NewRouter()
r.HandleFunc("/users/{id}", userHandler).Methods("GET")
req := httptest.NewRequest("GET", "/users/123", nil)
rr := httptest.NewRecorder()
// 手动注入 URLVars
vars := map[string]string{"id": "123"}
req = mux.SetURLVars(req, vars)
r.ServeHTTP(rr, req)
为什么不能直接用net/http/httptest测客户端代码
httptest专为测试http.Handler设计,不模拟服务端监听或网络往返。如果你在测 HTTP 客户端(比如用http.Client发请求),应该用httptest.NewUnstartedServer或更推荐的gock、httpmock等库拦截 outbound 请求。
常见误用:用NewRecorder去“接收”客户端发的请求——这是方向反了。客户端需要的是 mock server,不是 recorder。
-
NewUnstartedServer返回*httptest.Server,可调用Start()启动,URL属性即 mock 地址 - 用
ts.URL替换客户端配置中的 base URL,即可把真实请求导向本地 mock - 务必在测试结束调用
ts.Close(),否则端口泄漏,后续测试可能失败
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"ok":true}`))
}))
ts.Start()
defer ts.Close()
// 然后配置 client.BaseURL = ts.URL
// 再调用 client.Do(...) —— 请求将打到 ts
测试 HTTP handler 的关键不在“写得多”,而在“控制得准”:请求能带齐 header/body/context,响应能精确断言 Code/Body/Header。最容易被忽略的是路由参数注入和客户端/服务端测试场景混淆——前者不填URLVars就永远拿不到:id,后者用错NewRecorder就根本测不到真实行为。










