测自己写的 handler 用 httptest.newrecorder 就够了——90% 的测试无需端口,只需构造真实请求、捕获响应并分步断言状态码、header 和 body;而测客户端调第三方 api 或含中间件/路由的完整链路时,必须用 httptest.newserver。

测自己写的 handler,用 httptest.NewRecorder 就够了
别一上来就起服务——90% 的 handler 测试根本不需要端口。你真正要验证的是「这个函数收到请求后,是否写了正确的状态码、header 和 body」,而不是「它能不能被 TCP 连上」。
关键点在于:构造真实请求 + 捕获响应输出 + 分步断言。
-
http.NewRequest必须显式传 method、path、body;nilbody 表示空体,JSON 要用strings.NewReader(`{}`)并设Content-Type: application/json -
httptest.NewRecorder()返回的*httptest.ResponseRecorder不是标准http.ResponseWriter实现,但兼容所有写入操作,可直接读.Code、.Body.Bytes()、.Header() - 务必先断言
rec.Code == http.StatusOK,再检查rec.Body.String();否则 400/500 时 body 为空,你会误以为逻辑没执行 - 如果 handler 读 context(如超时或 trace ID),得用
req = req.WithContext(context.WithValue(...))注入,否则取不到
测客户端调第三方 API,必须用 httptest.NewServer
当你写的代码里有 http.Client.Do 或 http.Get,且目标是外部服务(比如支付网关、短信平台),那就得模拟一个真实 HTTP 端点——因为你要验证的不是 handler 逻辑,而是「请求发没发对、header 带没带全、重试有没有触发」。
它启动的是真实监听套接字,走完整 TCP 生命周期,中间件、DNS 解析、连接池、超时都参与其中。
-
srv := httptest.NewServer(http.HandlerFunc(...))后,srv.URL是形如http://127.0.0.1:34212的地址,**永远是 HTTP**;即使你要 mock HTTPS 接口,也得用http://协议访问 - 被测代码里不能硬编码
"https://api.example.com",必须把 base URL 抽成字段或参数,测试时注入srv.URL -
defer srv.Close()是强制项,漏掉会导致端口泄漏、后续测试失败;Go 1.22+ 还会报 goroutine 泄漏警告 - handler 里若需返回非 200 状态码,必须显式调
w.WriteHeader(404);只写json.NewEncoder(w).Encode()默认全是 200
测含中间件或路由的完整服务链路,也得用 httptest.NewServer
如果你用 Gin/Echo/http.ServeMux,并且中间件(鉴权、日志、CORS)会影响响应结果,那 httptest.NewRecorder 就绕过了它们——它只调 handler 函数,不走 ServeHTTP 链路。
这时候要把整个 http.Handler(比如 router 或 engine)丢给 NewServer,让请求真地流过注册的中间件和路由匹配逻辑。
- 传给
NewServer的必须是实现了http.Handler的对象,比如http.ServeMux、gin.Engine、echo.Echo,不能是单个http.HandlerFunc - 不要在 handler 里手动解析
r.URL.Path做 if 判断;要用http.ServeMux注册路径,或框架原生路由机制,否则无法覆盖路由未命中等边界 - 框架自带的测试辅助(如
gin.CreateTestContext)本质是包装NewRecorder,对中间件支持不一致;简单场景建议直接用原生http.ServeMux+NewServer更可控 -
NewServer不支持 TLS;要测 HTTPS 行为,得用httptest.NewUnstartedServer手动配srv.TLS,再调srv.StartTLS()
别碰 httpmock,除非你完全没法改被测代码
它只在一种情况值得考虑:闭源 SDK、遗留二进制依赖、或者你连 handler 入口都拿不到——比如某个第三方库内部硬编码了 http.DefaultClient 并直接发起请求。
但它极易静默失效:一旦被测代码用了自定义 http.Client、改了 transport、或用了 context timeout,httpmock 就捕获不到请求,测试还绿着,实际跑飞了。
- 它 hook 的是
http.DefaultTransport,不是整个网络栈;任何绕过它的 client 都逃逸 - 没有端口、不走 TCP,跟真实环境偏差大;超时、重定向、连接复用等行为无法验证
- 相比
httptest.NewServer,它多一层间接、少一层保障;能用标准库就别引入额外 mock 工具
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











