90%的http单元测试只需httptest.newrequest+httptest.newrecorder:轻量、不占端口、不走网络、秒级执行;测完整链路(含中间件/路由)或客户端行为时才需httptest.newserver,并注意端口管理与依赖注入。

别用 http.Get 测 handler,也别一上来就 httptest.NewServer 起服务——90% 的 HTTP 单元测试只需 httptest.NewRequest + httptest.NewRecorder,不占端口、不走网络、秒级执行。
测单个 handler 函数:用 NewRequest + NewRecorder
这是最轻量、最常用的方式,适合验证 JSON 序列化、状态码、重定向逻辑、Header 设置等。核心是把 handler 当普通函数调用,不经过路由或中间件。
-
httptest.NewRequest构造请求:路径带查询参数就拼?key=val,POST JSON 必须用strings.NewReader(`{}`)并显式设Content-Type: application/json -
httptest.NewRecorder捕获响应:它不是真实ResponseWriter,但兼容所有写入操作;rr.Code是状态码,rr.Body.Bytes()是响应体,rr.Header()可查 Header - 必须显式调用
handler.ServeHTTP(rr, req),漏掉这行等于没跑逻辑 - 常见 panic 来自传
nil给ServeHTTP,或 handler 内部调用http.Error后没return,导致后续代码覆盖状态码
测含中间件/路由的完整链路:必须用 NewServer
当你用了 Gin、Echo、chi 或自定义 http.ServeMux,且中间件(如 JWT 鉴权、CORS、日志)参与响应流程时,NewRecorder 会跳过它们——必须用 NewServer 把整个 handler 树跑起来。
-
httptest.NewServer启一个真实本地 HTTP 服务,返回随机端口的srv.URL(如http://127.0.0.1:42103),客户端代码必须用它,不能硬写固定地址 - 每次调用都绑定新端口,务必
defer srv.Close(),否则测试间端口耗尽、goroutine 泄漏 - 不支持 HTTPS,默认只跑 HTTP;若需 TLS 测试,得用
httptest.NewUnstartedServer手动配srv.TLS - 注册路由时,要确保用的是你生产环境一致的 router 实例(比如
chi.NewRouter()),而不是临时 new 一个空 mux
测 HTTP 客户端行为(比如重试、跳转、Header 透传):也得用 NewServer
如果你的代码是发请求的一方(比如调第三方 API),想验证它是否正确设置了 Authorization、是否按预期重定向、是否处理了 401 后自动刷新 token,就得让客户端连上一个可控的模拟服务。
- 用
httptest.NewServer提供一个可预测响应的 endpoint,比如返回 302 +Location,或返回 401 + 自定义 header - 你的客户端代码用标准
http.Client去请求srv.URL,就像调真实服务一样 - 别试图用
NewRecorder“模拟响应”给客户端——它根本收不到,客户端只认真实 HTTP 连接 - 如果要 mock 多个外部域名(如同时调
api.a.com和auth.b.com),httptest不够用,该上httpmock
容易被忽略的细节:Body 为空、JSON 字段类型错、Host 缺失
这些不是 bug,是测试 setup 没到位。
-
recorder.Body.Bytes()为空?先检查 handler 是否真写了响应:有没有提前return、panic 了、或io.Copy写到了http.NoBody - JSON 解析失败?
map[string]interface{}中数字默认是float64,别直接跟int比;字段名大小写也要跟结构体 tag 对齐 - 路由匹配失败?很多路由器(如 chi、gorilla/mux)依赖
req.Host或req.URL.Scheme做 Host-based 路由或 HTTPS 判断,测试时记得设req.Host = "example.com" - 表单解析为空?
req.ParseForm()不是自动调的,handler 里用了req.FormValue就得在测试里手动调一次
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











