单元测试 Gin handler 时不应调用 router.Run(),而应使用 httptest.NewRequest 和 httptest.NewRecorder 手动调用 router.ServeHTTP(w, req),并显式管理中间件、绑定逻辑与错误处理。

测试 Gin handler 时别调用 router.Run()
单元测试里起真实 HTTP 服务器(比如调 router.Run() 或监听端口)是错的——这属于集成测试,慢、不稳定、难 debug。Gin 的 handler 完全可以脱离网络栈运行。
正确做法是用 httptest.NewRequest 构造请求,用 httptest.NewRecorder 捕获响应,再手动调 router.ServeHTTP(w, req)。
- 请求 body 是 JSON?用
strings.NewReader(`{"key":"val"}`)包一层传给http.NewRequest - 带 URL 参数(如
/user/:id)?路径里直接写值,比如"/user/123",Gin 会自动解析c.Param("id") - 带 query 参数(如
?page=2)?把参数拼进路径,或用req.URL.RawQuery = "page=2" - 别忘了 import
"net/http/httptest"和"net/http"
中间件在测试中不会自动生效
你在 main.go 里写的 r.Use(authMiddleware),对测试文件里新 gin.New() 出来的 router 完全无效。测试上下文是干净的,不继承任何全局配置。
漏掉中间件最常导致两种失败:线上 401 却测试通过(因为没走 auth),或测试里 404/400 却线上正常(比如 CORS 中间件没注册,测试时跨域被拦)。
立即学习“go语言免费学习笔记(深入)”;
- 把 router 初始化逻辑抽成函数,例如
func NewRouter(mws ...gin.HandlerFunc) *gin.Engine,测试时传 mock 版本 - JWT 验证类中间件,测试时用固定 token + mock 签名验证逻辑,别连真实密钥服务
- 日志、panic 捕获等中间件可直接跳过,除非你专门测它们
-
gin.TestMode只影响日志和 panic 处理,不影响中间件注册与执行
c.BindJSON() 和 c.ShouldBindJSON() 行为差异极大
这是 Gin 测试里最隐蔽的坑:用 c.BindJSON(&v) 时,一旦 JSON 解析失败(字段类型错、必填字段缺失),Gin 会直接写 400 响应并中断 handler 执行;而你如果只检查 w.Body.String() 却没看 w.Code,就会误判为“返回空但成功”。
c.ShouldBindJSON(&v) 则只校验、不自动响应,把控制权交还给你——这才是单元测试需要的可控行为。
- handler 里优先用
c.ShouldBindJSON(),自己决定怎么处理错误 - 测试时主动构造非法 JSON(如字符串字段传数字),验证是否返回 400 而非 200 + 空体
- 若必须用
BindJSON,测试断言里必须包含w.Code == http.StatusBadRequest
别依赖 gin.Default() 的默认中间件
gin.Default() 自动加了 Logger() 和 Recovery(),但在测试中它们可能干扰断言(比如日志写到 w.Body)或掩盖真实 panic(Recovery 捕获后返回 500,你却以为是业务逻辑问题)。
更可控的方式是用 gin.New(),按需显式添加中间件。
- 单元测试首选
gin.New(),避免隐式行为 - 需要日志调试?临时加
router.Use(gin.Logger()),但别让它影响响应体 - 想测 panic 场景?关掉
Recovery,让 panic 冒泡,用testify/assert.Panics捕获 - 测试并发安全?
*gin.Engine本身线程安全,但 handler 里的共享变量(如全局 map)仍需加锁或改用 sync.Map


















