测试 gin handler 必须设 gin.testmode 以禁用调试日志和 panic 恢复,避免输出干扰、响应被吞及堆栈丢失;应使用 httptest.newrequest/recorder 在内存中测试,正确设置请求头与参数,并用 c.shouldbindjson 和 assert.jsoneq 确保断言准确。

测试前必须设 gin.TestMode
不设这个,gin.Default() 会启用调试日志和 panic 恢复中间件,导致测试输出刷屏、c.AbortWithStatusJSON 被吞掉、断言失败时看不到真实错误堆栈。最典型表现是:handler 明明该返回 500 却测出 200,或者日志混在响应体里让 w.Body.String() 断言失败。
正确做法是在每个测试函数开头加:
func TestIndexHandler(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New() // 别用 Default()
r.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) })
// ...
}
- 不要只在
TestMain里设一次——多个测试并行跑时可能被覆盖 -
gin.TestMode不影响路由注册、中间件执行或绑定逻辑,只关掉日志和 Recovery - 如果用了
gin.Default(),记得先gin.SetMode(gin.TestMode)再调它
httptest.NewRequest + httptest.NewRecorder 是唯一正路
别调 r.Run() 或 httptest.NewServer() —— 那是集成测试,慢、依赖端口、不可控。Gin handler 本质是 func(*gin.Context),完全能在内存里喂请求、收响应。
构造请求要注意三件事:
- URL 参数(如
/user/:id)直接写死路径:"/user/123",Gin 会自动解析c.Param("id") - Query 参数拼在 URL 后:
"/users?page=2&size=10",别手动塞c.Request.URL.RawQuery(除非你要测 URL 解析逻辑) - POST/PUT 的 JSON body 必须设头:
req.Header.Set("Content-Type", "application/json"),否则c.ShouldBindJSON()直接返回err != nil
示例:
req := httptest.NewRequest("POST", "/login", strings.NewReader(`{"user":"a","pwd":"b"}`))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
用 c.ShouldBindJSON,别用 c.BindJSON
c.BindJSON 是“自动失败型”:一遇到字段类型错、必填字段空、JSON 格式非法,就直接写 400 响应并中断 handler。你若只检查 w.Body 却漏看 w.Code,就会误判为“业务逻辑没跑”。
c.ShouldBindJSON 只校验、不响应,把控制权交还给你——这才是单元测试要的确定性。
- handler 里优先写
if err := c.ShouldBindJSON(&v); err != nil { c.AbortWithStatusJSON(400, ...) } - 测试时主动造坏数据:
strings.NewReader(`{"user":123}`)(字符串字段传数字),断言w.Code == 400 - 如果非要用
BindJSON,测试断言必须包含w.Code和w.Body两部分
比对 JSON 响应必须用 assert.JSONEq
Go map 序列化后 key 顺序不固定,assert.Equal(t, `{"a":1,"b":2}`, w.Body.String()) 极易因字段顺序抖动失败。更糟的是,如果 handler 返回的是结构体指针或嵌套 map,序列化结果可能带空格/换行,字符串比对直接挂。
assert.JSONEq 会解析两边 JSON 后做语义比对,容忍格式差异、key 顺序、多余空白。
- 别用
assert.Equal比 JSON 字符串,除非你在测 Swagger Schema 这类对格式敏感的输出 - 如果 handler 返回二进制(如
c.Data)或自定义编码,改用w.Body.Bytes()+bytes.Equal - 注意:
assert.JSONEq要求两边都是合法 JSON 字符串,空响应体或 HTML 会 panic
真正难的不是写通测试,而是把 handler 里所有隐式依赖(DB、Redis、HTTP client)抽成接口,再用 fake 实现——否则你写的就不是单元测试,只是披着测试外衣的线上调用。











