如何使用Golang net/http/httptest模拟HTTP请求_进行接口测试

net/http/httptest 是 Go 中最标准、轻量且可靠的 HTTP 接口单元测试方式,通过内存管道直接调用 handler,无需网络开销;常用 httptest.NewRecorder 验证响应,NewServer 模拟完整客户端行为。

使用 net/http/httptest 是 Go 中进行 HTTP 接口单元测试最标准、轻量且可靠的方式。它不启动真实网络服务,而是通过内存中的请求-响应管道直接调用你的 handler,速度快、隔离性好、无需端口占用。

创建测试用的 Handler 和 Server

核心是把你的 HTTP handler(比如一个 http.HandlerFunc 或实现了 http.Handler 接口的结构体)传给 httptest.NewServer 或直接用 httptest.NewRecorder 配合 handler.ServeHTTP 调用。

  • 若只需验证响应内容(最常见),用 httptest.NewRecorder() + 手动调用 ServeHTTP
  • 若需完整模拟客户端行为(如重定向、Cookie 持久化、多次请求),用 httptest.NewServer(handler),它会启动一个临时监听地址

基础示例:测试 JSON 接口返回

假设你有如下 handler:

func HelloHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"message": "hello"})
}

对应测试写法:

func TestHelloHandler(t *testing.T) {
    req := httptest.NewRequest("GET", "/hello", nil)
    w := httptest.NewRecorder()

    HelloHandler(w, req) // 直接调用,无网络开销

    if w.Code != http.StatusOK {
        t.Fatalf("expected status OK, got %d", w.Code)
    }

    if w.Header().Get("Content-Type") != "application/json" {
        t.Error("Content-Type header not set correctly")
    }

    var resp map[string]string
    if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
        t.Fatal(err)
    }
    if resp["message"] != "hello" {
        t.Error("unexpected message")
    }
}

测试带路径参数或查询参数的接口

httptest.NewRequest 支持构造任意 URL 和请求头:

  • 带 query:用 "/api/users?id=123&name=john"
  • 带 path param(如 /:id):需配合路由库(如 gorilla/muxchi)解析;net/http 原生不解析,需手动设置 r.URL.Path 或使用其 URL.Query() 提取参数
  • 带 body(如 POST JSON):用 strings.NewReader(`{"name":"test"}`) 作为第三参数,并设置 Content-Type: application/json

测试中间件或依赖注入的 Handler

如果 handler 依赖数据库、配置或上下文值,推荐将依赖抽象为字段或参数,测试时传入 mock 实现:

type UserHandler struct {
    Store UserStore // 接口类型
}

func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    user, _ := h.Store.Get(r.URL.Query().Get("id"))
    json.NewEncoder(w).Encode(user)
}

测试时传入一个实现 UserStore 的 fake 结构体即可,无需真实 DB 连接。

避免常见陷阱

  • 忘记检查 w.Code —— 默认是 200,但 handler 可能返回 404/500 等
  • w.Body.String() 前没确认编码是否正确(特别是中文),建议统一用 w.Body.Bytes() 解析
  • 在测试中启动 NewServer 后未调用 .Close(),可能导致 goroutine 泄漏(尤其在循环测试中)
  • 误以为 httptest 能测试 TLS、代理、DNS 等网络层行为 —— 它只模拟应用层逻辑

不复杂但容易忽略细节,掌握 NewRecorderNewRequest 的组合就能覆盖绝大多数接口测试场景。