单元测试中 panic 不会被 gin.Recovery 捕获,因 gin.TestEngine 不自动加载中间件栈,panic 会直接中断测试;需手动用 defer/recover 包裹 handler 并验证响应。

单元测试中 panic 不会被 gin.Recovery 捕获
直接用 gin.TestEngine 或 httptest.NewRequest 发起请求时,gin.Recovery() 中间件默认不生效——因为测试引擎不会自动加载 gin.Default() 的中间件栈,也不会触发真实 HTTP server 的 panic 恢复流程。你看到的 panic 会直接中断测试、打印堆栈并失败,而不是返回 500 响应。
- 即使你在测试前手动
r.Use(gin.Recovery()),它也只对 handler 执行期间的 panic 生效;但若 panic 发生在中间件自身(比如日志中间件里)、或c.Next()之前,仍可能逃逸 -
gin.New()创建的引擎默认不含任何中间件,gin.TestEngine更是精简版,连gin.RecoveryWithWriter都不会自动注册 - 测试中调用
r.GET("/path", handler)后直接r.ServeHTTP(),整个流程不经过 net/http server 的 recover 机制
用 test helper 包裹 handler 并显式 recover
最可靠的方式不是依赖中间件,而是把待测 handler 封装进一个带 defer/recover 的测试函数里,模拟 recovery 行为,并检查返回值是否符合预期结构。
- 不要在测试里启动真实 server,避免 goroutine 和状态干扰
- 构造一个干净的
*gin.Context(用gin.CreateTestContext),传给 handler - 在测试函数内写
defer func() { if r := recover(); r != nil { /* 记录、断言 */ } }() - handler 执行完后,检查
w.Body.String()是否含你期望的 JSON 错误字段(如"code":500)
示例:
func TestPanicInHandler(t *testing.T) {
r := gin.New()
w, c := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest("GET", "/test", nil)
<pre class="brush:php;toolbar:false;">// 手动包装 handler
handler := func(c *gin.Context) {
panic("boom")
}
defer func() {
if r := recover(); r != nil {
c.AbortWithStatusJSON(500, gin.H{"code": 500, "msg": "internal server error"})
}
}()
handler(c)
assert.Equal(t, 500, w.Code)
assert.Contains(t, w.Body.String(), `"code":500`)}
测试自定义 Recovery 中间件本身
如果你写了自己的 CustomRecovery(),就该单独测它——而不是靠跑完整路由链。重点验证:它能否捕获 panic、是否调用 c.AbortWithStatusJSON()、是否脱敏敏感信息。
- 用
gin.CreateTestContext构造上下文,不走路由匹配逻辑 - 将中间件作为普通函数调用:
CustomRecovery()(c),然后在c.Next()模拟 panic - 断言响应体内容,而非状态码(因为中间件不写 response body,只设 status + json)
- 特别注意:别在中间件测试里 panic 字符串以外的类型(如 struct),否则类型断言会失败
协程里的 panic 在测试中无法被 Recovery 捕获
这是最容易忽略的一点:Gin 的 Recovery 只作用于当前 goroutine。如果 handler 内启了 goroutine 并在里面 panic,测试中无论怎么加中间件都捕不到——那个 panic 会直接 crash 测试进程。
- 测试时必须确保所有 panic 都发生在主 goroutine(即 handler 函数体内)
- 若业务逻辑确有 goroutine,测试中应改用
sync.WaitGroup+ 显式recover,或改用errgroup等可传播错误的模式 - 上线前务必用
go vet -race和集成测试覆盖 goroutine panic 场景,单元测试本身对此无能为力


















