




httptest 可绕过网络层直接测试 HTTP 处理器,无需真实端口。需用 httptest.NewRequest 构造完整请求,httptest.NewRecorder 捕获响应,并注意 Content-Type、Body 重用、状态码断言及中间件、数据库、时间依赖的隔离测试。
httptest 启动假 HTTP 服务,不依赖真实端口
Go 的 net/http/httptest 是接口测试的核心,它绕过网络层,直接把请求喂给你的 http.Handler,既快又稳定。不需要起真实服务器、不用管端口冲突、也不怕并发干扰。
常见错误是手动调 yourHandler.ServeHTTP() 却忘了构造完整的 *http.Request —— 比如漏掉 Body 或没设 Content-Type,导致解析失败。
httptest.NewRequest() 构造请求,显式设置 Method、URL、Body 和 Header
httptest.NewRecorder() 接收响应,之后可断言 recorder.Code、recorder.Body.String()、recorder.Header()
http.ServeMux 或 Gin/Echo 等框架,传入的是它们的 Handler(如 router.ServeHTTP),不是裸函数json.Unmarshal 错误和 Content-Type大多数 Web 接口走 JSON,但测试时容易卡在两个地方:一是请求体没设 Content-Type: application/json,导致中间件或绑定逻辑跳过解析;二是响应体读取后没重置 Body,导致后续 json.Unmarshal 失败(Body 是单次读取流)。
示例中常见写法:
立即学习“go语言免费学习笔记(深入)”;
req := httptest.NewRequest("POST", "/api/users", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
var resp map[string]interface{}
// 注意:rec.Body.Bytes() 可多次调用,但 rec.Body.Read() 后需重置
err := json.Unmarshal(rec.Body.Bytes(), &resp)
rec.Code 是否为预期状态码(比如 200、400、401),别只看 JSON 结构error 字段io.ReadAll(r.Body),测试时传入的 Body 必须是可重读的(bytes.Reader 或 strings.NewReader),不能是 nil 或临时 os.Stdin
不要等整个路由启动后再测“加了 Auth 中间件是否拒绝无 token 请求”——那样耦合太重,失败时难定位。应该把中间件当成普通函数来测:输入 *http.Request → 输出是否调用了 next.ServeHTTP,或是否写了特定响应。
例如一个 JWT 中间件:
func JWTMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "missing token", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
}
)
}
Authorization 头的请求,传给包装后的 handler,断言响应码是 http.StatusUnauthorized
httptest.NewRecorder() 检查是否真正调用了 next(可在 next 里打日志或设标记变量)func(string) (bool, error) 作为依赖注入点Web 接口测试一旦连上真实 PostgreSQL/MySQL,就变成集成测试,慢、不稳定、还污染数据。Golang 测试中更推荐两种轻量方案:
"sqlite3://file::memory:?cache=shared&_fk=1",每次测试都是干净 DB,支持外键BEGIN + ROLLBACK 包裹测试逻辑(通过 db.Begin() 获取事务,测试完 tx.Rollback()),前提是 handler 支持传入 *sql.Tx 而非固定用 *sql.DB
关键点在于:handler 层要能接收可替换的数据访问依赖(比如接受 userRepo UserRepo 接口而非直接 new MySQLUserRepo),否则测试时无法隔离。
容易被忽略的是时间字段(如 created_at)—— 测试中若用 time.Now(),会导致断言不稳定;应统一用可注入的 clock Clock 接口,测试时固定返回某个时间值。