★ httptest 单元测试 Handler 与中间件

结论先行

测试 gin handler/中间件不需要起真实端口:用 httptest.NewRecorder() 当 ResponseWriter,httptest.NewRequest() 构造请求,然后直接 engine.ServeHTTP(rec, req)——完整走一遍路由+中间件+handler,然后断言 rec.Coderec.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 上。
笔记加载中…