直接读 w.Code 就行,它准确反映最终状态码;必须调用 router.ServeHTTP(w, req) 触发完整生命周期,测 Gin 时需设 gin.SetMode(gin.TestMode),并为每个测试新建 httptest.NewRecorder 实例。

直接读 w.Code 就行,别绕弯
Go 的 httptest.NewRecorder() 返回的响应记录器对象自带 Code 字段,它就是最终写入的 HTTP 状态码。中间件、Handler、甚至 panic 后的 recovery 中间件触发的 500,都会如实反映在这里。不需要解析响应体、也不用 mock net/http 的底层逻辑。
常见错误是误以为要从 c.Status() 或 c.Writer.Status() 取值——这些在测试中不可靠:前者只在显式调用时更新,后者在未包装 ResponseWriter 时始终为 0。
- 必须用
router.ServeHTTP(w, req)触发完整请求生命周期,否则w.Code保持初始值 0 - 如果测的是带中间件的路由,确保中间件已通过
router.Use()或group.Use()注册,否则中间件根本没执行 - 别在测试里手动调
c.AbortWithStatus(404)后还去断言w.Code—— 这会覆盖真实状态,属于干扰测试流程
gin.TestMode 不设,w.Code 可能不准
默认模式下,Gin 会启用调试中间件(比如重定向 301/302 自动转 307、日志刷屏、panic 捕获后返回 500 但不写 body),这些行为会污染真实状态码。例如一个本该返回 404 的路由,在非 TestMode 下可能被重定向中间件劫持成 301,w.Code 就变成 301 而不是你期望的 404。
正确做法是在每个测试函数开头或 TestMain 里加一句:
gin.SetMode(gin.TestMode)
- 这个设置影响整个测试进程,不能只在某个 test 函数里设完就完事——后续 test 可能复用旧状态
- 设了
TestMode后,Gin 不再自动处理重定向、不打印调试日志、panic 会透出到测试框架,让w.Code更贴近 handler 实际输出 - 如果你发现
w.Code总是 200,但手动 curl 是 404,第一反应就该查gin.Mode()是不是还是debug
断言 4xx/5xx 时,得让 handler 真正走到出错分支
很多测试失败是因为 handler 逻辑没触发错误路径。比如你写了 c.JSON(400, gin.H{"error": "bad request"}),但测试请求没传必要参数,结果 handler 在 c.ShouldBindJSON() 阶段就返回了 400 —— 这个 400 是 Gin 内置绑定器打的,不是你写的那行代码;或者更糟:你忘了注册路由,router.ServeHTTP 直接返回 404,而你以为是自己逻辑返回的。
- 构造请求时,必须匹配路由定义:URL 路径要带参数占位符值(如
/user/123),查询参数拼在 URL 后(如/search?q=test) - POST/PUT 请求必须设
Content-Type头,否则c.ShouldBindJSON()会静默失败并返回 400,但你可能没意识到这是绑定失败而非业务校验 - 想验证自定义错误码,最好在 handler 开头加
c.Abort()+c.Status(422),再断言w.Code == 422,避免依赖下游逻辑是否执行到
并发测试下 w.Code 不会串,但别复用 *httptest.ResponseRecorder 实例
httptest.NewRecorder() 返回的是干净的、无状态的对象,每次调用都新建,所以并发测试天然安全。但有人图省事在 test 函数外定义一个全局 w 变量,多个 goroutine 共用,就会出现 A 测试还没读完 w.Code,B 测试已经把新值写进去了。
真正容易被忽略的是中间件内部对 ResponseWriter 的包装逻辑:如果你的日志中间件用了自定义 responseWriter 并缓存了 status 字段,又没在每次请求 new 一个实例,那么并发请求之间 status 值就会互相覆盖——这时 w.Code 没问题,但中间件自己记录的日志字段就错了。
- 每个测试用例必须调一次
httptest.NewRecorder(),不要缓存复用 - 自定义
ResponseWriter包装器必须是 request-scoped,不能跨请求共享字段 - 验证方式:起两个 goroutine 分别发 200 和 404 请求,然后 assert 各自的
w.Code,看是否稳定不交叉


















