httptest 包用于在内存中模拟 http 请求-响应流程,不启动真实服务器;通过 httptest.newrecorder() 捕获响应,用 recorder.code、recorder.header() 和 recorder.body.string() 等断言结果,配合 httptest.newrequest() 构造请求,适用于 handler、中间件及主流框架路由测试。

httptest 包不是用来“测试 HTTP 协议”的,而是帮你把 http.Handler 当作黑盒,在内存中模拟一次完整请求—响应流程——不启动真实服务器、不走网络、不依赖端口。
用 httptest.NewRecorder() 捕获响应内容
这是最常用起点:你不需要启动监听,只需把 handler 和伪造的请求丢给 httptest.NewRecorder(),它会把响应头、状态码、响应体都存进内存里供断言。
常见错误是直接用 fmt.Println 打印响应体却忘了 recorder.Body.String() 或 recorder.Body.Bytes() ——recorder.Body 是 *bytes.Buffer,不调方法拿不到内容。
- 用
recorder.Code断言状态码(比如200、404) - 用
recorder.Header().Get("Content-Type")检查响应头 - 用
recorder.Body.String()获取文本响应,json.Unmarshal(recorder.Body.Bytes(), &v)解析 JSON - 别对
recorder.Body本身做 == 比较,它不是字符串
用 httptest.NewRequest() 构造各种请求场景
httptest.NewRequest() 是构造请求的唯一推荐方式,它返回标准 *http.Request,可安全传给任何 http.Handler.ServeHTTP()。
立即学习“go语言免费学习笔记(深入)”;
注意几个易错点:
- 方法名必须大写:
"GET",不是"get" - URL 路径要带前导
/,比如"/api/users",否则req.URL.Path可能为空 - POST/PUT 请求需手动设置
Body并调用req.ParseForm()或req.ParseMultipartForm(),否则req.FormValue返回空 - 要测试 JSON 接口?记得设
req.Header.Set("Content-Type", "application/json"),再写入 JSON 字节到req.Body
测试带中间件或路由的 handler 链
httptest 对中间件完全透明:你只要把最终组合好的 http.Handler(比如 middleware(handler) 或 r.ServeHTTP())传进去就行。
关键在于「谁负责初始化」:
- 如果 handler 依赖数据库连接、配置等,测试时得提前注入 mock 或 test-only 实例,不要在 handler 内部 new 出来
- 用
http.HandlerFunc包一层闭包,把依赖变量捕获进来,比全局变量更可控 - 测试 Gin/Echo/Chi 等框架路由时,别测
router.GET(...)注册逻辑,而是调用router.ServeHTTP(recorder, req) - 若 handler 内部调用了
http.Redirect,recorder.Code会是302,recorder.Header().Get("Location")可断言跳转地址
避免在测试里用 httptest.NewUnstartedServer()
这个函数会真正启动一个监听本地端口的 server,仅在极少数需要测试客户端行为(如重试、超时、HTTP/2 特性)时才用。绝大多数 handler 单元测试根本不需要它。
滥用后果:
- 端口被占用导致并发测试失败(尤其 CI 环境)
- 忘记调用
server.Close()导致 goroutine 泄漏 - 引入网络延迟,让本该毫秒级的单元测试变慢
- 掩盖 handler 本身的问题(比如它其实依赖了
http.DefaultClient)
真正难测的从来不是“能不能跑通”,而是 handler 在边界输入、panic、error 返回、并发调用下的表现——这些用 NewRecorder + NewRequest 就足够覆盖。










