用 httptest.NewRecorder 测试 handler 是最直接的方式:不启端口、不走网络,直接调用 handler 并捕获内存中响应,需手动构造请求(含 method、URL、body)、注入路径变量、设置 header,并结合 mock 依赖验证业务逻辑与错误路径。

用 httptest.NewRecorder 测试 handler 是最直接的方式
Go 标准库的 net/http/httptest 就是为这事设计的:不启端口、不走网络、直接把请求“喂”给你的 handler,再抓取响应结果。这是单元测试 HTTP 逻辑的默认路径,也是最快最隔离的做法。
常见错误是误用 httptest.NewServer 去测单个 handler——它会真实监听本地端口,启动慢、有资源泄漏风险、还可能被防火墙或端口占用干扰,纯属大炮打蚊子。
- 构造请求用
http.NewRequest,注意 method、URL 和 body 都要匹配真实调用场景 - 捕获响应必须用
httptest.NewRecorder(),它实现了http.ResponseWriter接口,所有写入都进内存,可随时读取rr.Code、rr.Body.String()、rr.Header() - 调用方式就是
yourHandler(rr, req)(如果是http.HandlerFunc)或yourMux.ServeHTTP(rr, req)(如果是路由实例)
测试带参数的接口:路径变量、查询参数、JSON body 要分别模拟
真实请求里这三类数据不会自动“塞进 handler”,得你手动构造,否则 handler 拿到空值就容易 panic 或返回 400。
比如用 gorilla/mux 时,路径参数 /user/{id} 不会自动解析进 r.URL.Path,得靠 mux.SetURLVars(req, map[string]string{"id": "123"}) 注入;而 Gin/Echo 等框架则需走其上下文机制,不能跳过。
- 查询参数:拼在 URL 里,如
http.NewRequest("GET", "/api/users?id=7&limit=10", nil) - 路径变量:用对应路由库的工具函数注入,例如
mux.SetURLVars(req, map[string]string{"id": "456"}) - JSON body:用
strings.NewReader(`{"name":"alice"}`)构造,并显式设置req.Header.Set("Content-Type", "application/json")
别只断言状态码,要验证业务逻辑是否真被执行
写个 if rr.Code != 200 过了测试,不代表逻辑对了——可能 handler 里数据库调用失败但没返回错误,也可能 JSON 返回字段全为空却仍返回 200。这类测试只是“HTTP 层及格”,不是“业务层可信”。
关键是要把外部依赖(DB、缓存、第三方 API 客户端)抽成接口,测试时注入 mock 实现,再对 mock 行为做断言。
- 例如 handler 里调用了
userRepo.FindByID(id),测试时传入一个记录调用次数和参数的 mock,断言它是否被调用一次、参数是否为 "123" - 响应体优先用
json.Unmarshal解码成 struct,再断言字段值;比用字符串匹配rr.Body.String()更健壮,也防 JSON 格式微调导致误报 - 错误路径也要覆盖:比如 DB 返回
sql.ErrNoRows,handler 是否返回 404?是否写了正确 error message?
框架适配要点:Gin、Chi、Echo 的测试入口不统一
直接把 *http.Request 丢给 gin.Engine 会 panic,因为 Gin 的 ServeHTTP 依赖内部上下文初始化。不同框架的测试入口差异明显,硬套标准 http.Handler 方式会失败。
本质区别在于:标准库 handler 是函数式,框架则是封装了中间件链、上下文、绑定逻辑的完整运行时。测试时必须走框架推荐的测试路径。
- Gin:用
gin.CreateTestContext+engine.ServeHTTP,或更简洁地直接r := gin.Default(); r.POST(...); r.ServeHTTP(rr, req) - Chi:直接
chi.NewRouter().ServeHTTP(rr, req)即可,它兼容标准http.Handler接口 - Echo:需用
echo.New().NewContext构建 context,再调用 handler 的Handle方法,不能直传http.ResponseWriter
最容易被忽略的是中间件测试——比如 auth middleware 是否在未登录时拦截请求并返回 401?得把 middleware 包裹在 handler 外层,再用 mock next handler 观察是否被调用。










