★ httptest 单元测试 Handler 与中间件
结论先行
测试 gin handler/中间件不需要起真实端口:用 httptest.NewRecorder() 当 ResponseWriter,httptest.NewRequest() 构造请求,然后直接 engine.ServeHTTP(rec, req)——完整走一遍路由+中间件+handler,然后断言 rec.Code、rec.Body。测中间件就是“把它 Use 到最小 engine 上,再打请求”。
测试套路
| 步骤 | 代码 |
|---|---|
| 准备 engine | 测试模式 gin.SetMode(gin.TestMode);gin.New() + Use 被测中间件 + 注册路由 |
| 造请求 | httptest.NewRequest(method, path, body),可再设 Header |
| 执行 | engine.ServeHTTP(recorder, req) |
| 断言 | recorder.Code / recorder.Body.String();JSON 响应再反序列化比对 |
代码示例
func setupEngine() *gin.Engine {
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(jwtAuth()) // 被测中间件
r.GET("/users/:id", getUser) // 配合一个最小 handler
return r
}
func TestGetUserAuthorized(t *testing.T) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/users/42", nil)
req.Header.Set("Authorization", "Bearer "+signToken("u-1"))
setupEngine().ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("code = %d, body = %s", w.Code, w.Body.String())
}
var got struct {
ID string `json:"id"`
}
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
t.Fatal(err)
}
if got.ID != "u-1" {
t.Fatalf("unexpected payload: %+v", got)
}
}
func TestGetUserUnauthorized(t *testing.T) {
w := httptest.NewRecorder()
setupEngine().ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/users/42", nil))
if w.Code != http.StatusUnauthorized {
t.Fatalf("want 401, got %d", w.Code)
}
}
// 带 JSON body 的绑定测试
func TestCreateUser(t *testing.T) {
w := httptest.NewRecorder()
body := strings.NewReader(`{"name":"alice"}`)
req := httptest.NewRequest(http.MethodPost, "/users", body)
req.Header.Set("Content-Type", "application/json")
setupEngine().ServeHTTP(w, req)
if w.Code != http.StatusCreated { /* 断言... */ }
}
注意点
- 表驱动:把 method/path/body/wantCode 写成 case 数组,一个测试函数覆盖成功与各类失败分支。
- 每个用例新建 engine(或每个用例前重置全局状态),避免用例间串扰。
- 想测“真 socket/客户端行为”(如重定向、keep-alive),用
httptest.NewServer(engine)拿到真实地址。 - 中间件顺序断言:注册两个中间件各自往 body/header 追加标记,看最终顺序即可验证洋葱模型。
常见追问 / 记忆点
- 追问:httptest.NewRecorder 和 httptest.NewServer 区别?答:前者进程内直接喂给 ServeHTTP,快、无端口;后者起真实 HTTP 服务,适合端到端/客户端行为验证。
- 追问:怎么测绑定失败的“静默”坑?答:发缺必填字段的请求,断言返回 400 而非 200——能倒逼业务代码检查 ShouldBind 错误。
- 记忆点:NewRecorder + NewRequest + engine.ServeHTTP 三件套;测中间件就 Use 到最小 engine 上。