必须设gin.SetMode(gin.TestMode),否则gin.Default()会加载调试用Logger和Recovery中间件,导致日志刷屏、重定向触发、panic被捕获而断言失败;推荐在TestMain中统一设置,并确保自定义中间件在测试模式下跳过副作用。

必须设 gin.SetMode(gin.TestMode),否则中间件行为失真
不设测试模式,gin.Default() 会加载调试用的 Logger 和 Recovery 中间件,导致日志刷屏、重定向被触发、panic 被捕获后断言失败。比如你期望 400 错误返回 JSON,实际却收到 500 + HTML 页面——这根本不是业务逻辑问题,而是模式没切对。
推荐在 TestMain 里统一设置,避免每个测试函数重复写:
func TestMain(m *testing.M) {
gin.SetMode(gin.TestMode)
m.Run()
}
如果项目里用了自定义中间件(比如鉴权、日志),也要确认它们在 TestMode 下是否跳过副作用(如写磁盘、打远程日志),否则测试会变慢或不稳定。
c.Param()、c.Query()、c.ShouldBindJSON() 各自的请求构造要点
URL 参数、查询参数、JSON Body 是三类最常见输入,但构造方式完全不同,混用就 404 或空值:
-
c.Param("id"):路由必须注册为router.GET("/users/:id", handler),测试时 URL 写成"/users/123",不能写成"/users?id=123" -
c.Query("name"):直接拼在 URL 后,如"/users/123?name=test&role=admin",无需手动解析 -
c.ShouldBindJSON(&v):必须设请求头req.Header.Set("Content-Type", "application/json"),且 body 是合法 JSON 字符串;否则绑定失败,v保持零值
常见错误是把 JSON 当字符串传给 strings.NewReader 却忘了设 header,结果 c.ShouldBindJSON 直接返回 err != nil。
文件上传接口测试:别手写 multipart body,用 multipart.Writer
c.FormFile("file") 返回 nil 的根本原因,90% 是请求体不符合 multipart/form-data 规范——边界(boundary)、换行、字段名、Content-Type 头四者必须严格一致。
正确做法是交由标准库生成:
buf := &bytes.Buffer{}
writer := multipart.NewWriter(buf)
file, _ := writer.CreateFormFile("file", "test.png")
file.Write([]byte("fake-content"))
writer.Close() // 必须调用,否则 boundary 结束符缺失
req, _ := http.NewRequest("POST", "/upload", buf)
req.Header.Set("Content-Type", writer.FormDataContentType()) // 值来自 writer,不是硬编码
漏掉 writer.Close()、字段名写成 "avatar" 但 handler 里写 c.FormFile("file")、或者 header 里填了 "multipart/form-data; boundary=xxx" 但和 writer 生成的不一致——这三类错误都会让 Gin 跳过 multipart 解析,c.MultipartForm() 始终为 nil。
中间件测试要带真实上下文链,别只测 Handler
日志、鉴权、限流这类中间件依赖 *gin.Context 的生命周期,不能像普通函数那样单独调用。必须走完整路由链:
- 注册中间件:用
router.Use(authMiddleware)或router.GET("/api", authMiddleware, handler) - 模拟前置条件:比如鉴权中间件检查
c.Request.Header.Get("Authorization"),测试时就得req.Header.Set("Authorization", "Bearer xxx") - 验证副作用:如果中间件往
c.Set("user_id", "123")写了值,后续 handler 或断言里要用c.MustGet("user_id")拿出来校验
特别注意:中间件如果提前 c.Abort() 或 c.JSON(401, ...),handler 就不会执行——这时你要断言响应状态码和 body,而不是 handler 内部逻辑。
真正难测的从来不是 handler 本身,而是它和中间件、绑定逻辑、multipart 解析器之间的协作边界。这些地方一出错,现象往往是“返回空”“状态码不对”“参数拿不到”,但根因藏在请求构造或模式配置里,很容易浪费半小时排查。


















