Go本地接口测试首选httptest.NewRequest+NewRecorder做单元测试,不占端口、秒级完成;API联调需用http.ListenAndServe启动真实服务,而非httptest.Server,因其生命周期仅限测试函数内。

本地接口测试不需要额外装工具,Go 自带 httptest 就够用;API 联调则必须启动真实 HTTP 服务,但别用 httptest.Server —— 它不对外暴露,前端根本连不上。
怎么用 httptest 做快速单元测试?
这是验证 handler 逻辑最轻量的方式,不占端口、不启网络、秒级完成。
- 用
httptest.NewRequest构造任意请求:method、path、header、body 都能设,比如http.MethodPost+/api/user+Content-Type: application/json - 用
httptest.NewRecorder捕获输出:它模拟 ResponseWriter,能直接读rr.Code、rr.Body.Bytes()、rr.Header() - 别直接调
http.ListenAndServe启服务再 curl —— 那是手动调试,不是可重复的测试 - 常见坑:传空
*http.Request给 handler 导致r.URL.Query()panic;body 没用strings.NewReader包裹,读出来是空
为什么 httptest.Server 不能用于前端联调?
httptest.Server 是为集成测试设计的,它监听 localhost:0(随机端口),返回的 srv.URL 确实可被外部访问,但只限本机进程 —— 浏览器、Postman、前端开发服务器(Vite/Webpack)能连上,但它生命周期绑定在 test 函数里,test 结束就自动关了。
- 你写个
TestAPI启 server,跑完 test 就没了,前端连不上 - 想让它常驻?得手写
http.ListenAndServe(":8080", handler),并确保只绑127.0.0.1(默认就是) - 别用
net/http/httptest替代真实服务 —— 它不是代理,也不是 Mock Server,只是测试辅助
如何启动一个真正可用的本地 Mock Server?
目标很明确:前端不改代码,就能把请求打到你的 Go 服务上。关键不在 Go 怎么写,而在怎么接入。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 启动服务时用
http.ListenAndServe(":8080", mux),别用nil—— 如果用了路由库(Gorilla Mux / Gin),传具体 router 实例 - 前端开发服务器配 proxy:Webpack/Vite 的
proxy把/api前缀转发到http://localhost:8080,这是最干净的切换方式 - 别在前端代码里写
if (process.env.NODE_ENV === 'development')切域名 —— 环境变量漏配置或误发布,线上就调 Mock - 如果必须用域名(如
api.example.com),改本地/etc/hosts把它指向127.0.0.1,Go 服务监听:80(需 sudo)或用 nginx 反代
Mock 第三方 API 时要注意什么?
你的接口依赖外部服务(比如调了 https://openai.com/v1/chat/completions),测试时不想真发请求?用 gock 拦截 outbound 请求。
- 必须在实际使用的
http.Client上启用:gock.EnableNetworking()不要开,否则还是走网络;gock.InterceptClient(client)才对 - 每条 mock 规则要精确匹配 method + URL + header(比如
Authorization),否则拦截失败 - 测试末尾一定要调
gock.Off(),不然 mock 会污染后续测试 —— 这是高频翻车点 -
gock只作用于 HTTP client 层,不影响你自己的 handler,所以它和httptest可以共存
真正容易被忽略的是:联调时的端口冲突和生命周期管理。多人共用一台机器开发,:8080 很可能被占;CI 里并行跑测试,硬编码端口必报 address already in use。解决办法不是换端口,而是让 Go 自动选 —— 用 http.ListenAndServe("127.0.0.1:0", handler),然后从 listener.Addr().String() 提取实际端口,再喂给前端 proxy 配置。

















