用 httptest.NewServer 或 httptest.NewRecorder 可模拟 HTTP 生命周期:前者测客户端行为并开真实端口,后者轻量直接调 handler 适合单元测试;需避免传 nil handler 和漏调 server.Close(),并注意依赖隔离、状态码与 JSON 响应的正确断言。

用 net/http/httptest 启动假服务器测接口
不用起真实服务,httptest.NewServer 或 httptest.NewRecorder 就能模拟完整 HTTP 生命周期。前者适合测试客户端行为(比如你写的 HTTP client 是否正确发请求),后者更轻量,直接调 handler 函数,跳过网络层,适合单元测试。
常见错误是直接传 nil 给 handler 导致 panic;或者忘了在测试末尾调用 server.Close(),导致端口被占、后续测试失败。
- 测路由和中间件逻辑 → 用
httptest.NewRecorder+ 手动构造*http.Request - 测跨服务调用或重定向行为 → 用
httptest.NewServer,它会开真实端口并返回*url.URL - 注意:若 handler 依赖全局状态(如数据库连接池、配置变量),测试前需重置或注入 mock,否则测试间会污染
如何写可断言的测试用例(含 JSON 和状态码)
Go 标准库不带断言库,得自己比对 recorder.Code、recorder.Body.String(),再用 json.Unmarshal 解析响应体。容易出错的地方是忽略空格、换行、字段顺序(JSON 解析不敏感,但字符串比对敏感)。
示例片段:
立即学习“go语言免费学习笔记(深入)”;
req, _ := http.NewRequest("GET", "/api/user/123", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
}
var resp map[string]interface{}
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatal("failed to unmarshal response:", err)
}
if resp["id"] != float64(123) { // 注意 JSON number 默认是 float64
t.Error("expected id=123")
}
- 状态码必须显式检查,不能只看响应体是否非空
- JSON 字段值类型要小心:
int、bool、null在map[string]interface{}中对应不同 Go 类型 - 若结构体固定,优先用具体 struct +
json.Unmarshal,比map更安全、易读
测试带中间件的 handler(如 JWT 验证、日志、CORS)
中间件本质是函数套函数:func(http.Handler) http.Handler。测试时要把目标 handler 包进中间件链里,再丢给 httptest —— 否则中间件逻辑完全没跑。
典型坑是忘记传入 context 或依赖未初始化的依赖项(比如中间件里调了 db.QueryRow,但测试时没 mock db)。
- 验证中间件是否生效:检查响应头(如
rec.Header().Get("X-Content-Type-Options"))、状态码(如未登录返回 401)、或副作用(如日志是否写入 buffer) - 若中间件依赖外部服务,用 interface 抽离,并在测试中注入 fake 实现(比如
type TokenValidator interface { Validate(string) (Claims, error) }) - 避免在测试中用
os.Setenv改全局环境变量——并发测试会冲突;改用局部配置变量或函数参数传入
怎么测超时、重试、错误响应这些边界情况
标准 http.Client 支持设置 Timeout、Transport,你可以用自定义 RoundTripper 模拟慢响应或连接中断。比如让 transport 在第 2 次请求时才返回,就能测重试逻辑。
更简单的方法是绕过 client,直接调用你的业务函数(比如 FetchUserInfo(ctx, userID)),然后传入带 cancel 的 context.WithTimeout,观察是否提前返回错误。
- 测试超时:用
context.WithTimeout(context.Background(), 1*time.Millisecond),确保 handler 内部用了该 ctx 做 I/O 控制 - 测试网络错误:自定义
http.RoundTripper返回net.ErrClosed或直接 panic,看上层是否捕获并降级 - 别依赖 sleep 等待异步完成——用
sync.WaitGroup或 channel 显式同步










