Gin单元测试不能直接构造裸gin.Context,因其c.Request、c.Params等字段为nil导致c.Param()、c.ShouldBindJSON()失效;必须用gin.CreateTestContext创建真实上下文,再注入httptest.NewRequest和手动设置Params、Header等。

为什么 Gin 单元测试不能直接调用 handler 函数
直接 c := &gin.Context{} 然后传给 handler 会 panic,因为 c.Request、c.Params、c.Keys 都是 nil,c.Param("id") 或 c.ShouldBindJSON() 全部失效。Gin 的上下文不是裸 struct,它依赖初始化链和中间件注入状态。
正确路径只有一条:用 gin.CreateTestContext 创建真实生命周期的 *gin.Context,再手动注入请求和参数。
- 必须调
req := httptest.NewRequest("GET", "/users/123", nil),再c.Request = req - 带路径参数?必须写
c.Params = gin.Params{{Key: "id", Value: "123"}},否则c.Param("id")返回空字符串 - POST JSON?别忘了
req.Header.Set("Content-Type", "application/json"),否则c.ShouldBindJSON()静默失败 - 中间件没生效?说明你没走
router.ServeHTTP()或没注册中间件——gin.CreateTestContext不自动加载中间件链
sqlmock 和 gomock 该用哪个、怎么配
它们解决不同层的问题:sqlmock 拦截 *sql.DB 调用,验证 DAO 层 SQL;gomock 生成 interface mock,隔离 service 层对 repository 的依赖。两者常一起用,但不能混用对象。
比如你有个 repo.UserRepo 接口,service 层依赖它——那就用 gomock 生成 MockUserRepo;而 DAO 层实现里用了 db.QueryRow(),就用 sqlmock 拦截并断言 SQL。
-
gomock前提:接口必须定义在被测模块外(如internal/repo/user.go),否则mockgen找不到 -
sqlmock默认严格模式,未声明的查询直接 panic;调试时可临时换sqlmock.New(sqlmock.QueryMatcherEqual)放宽匹配 - 别在 test 里写
defer db.Close()——sqlmock返回的db是假的,Close()会 panic - DAO 测试重点看 SQL 是否拼对、参数是否传准;service 测试重点看逻辑分支是否覆盖、error 是否透传
中间件单独测 vs 集成到路由测
中间件本质是 func(*gin.Context),单元测它只需传一个 mock context 进去,检查它是否调了 c.Abort()、c.Set() 或改了 c.Writer.Status();集成测则要把中间件注册进 gin.Engine,走完整请求流程,验证它是否影响下游 handler 行为(比如鉴权失败是否真返回 401)。
- 单元测中间件:用
gin.CreateTestContext+httptest.NewRequest构造c,手动设c.Request.Header.Set("Authorization", "xxx"),再调yourMiddleware(c) - 集成测中间件:注册到 router,比如
r.Use(authMiddleware),再走router.ServeHTTP(w, req),断言响应码和 body - 常见坑:中间件里调
c.Next()后又写了响应(如c.JSON(200, ...)),但没c.Abort(),会导致下游 handler 再次写响应 panic - 日志中间件或耗时统计中间件,适合单元测;JWT 验证、RBAC 权限中间件,必须集成测
测试时 gin.TestMode 忘设会怎样
不设 gin.SetMode(gin.TestMode),gin.Default() 会启用 gin.Logger() 和 gin.Recovery(),导致测试输出刷屏、panic 被 recover 后无法捕获、重定向被触发(比如 c.Redirect() 在非 TestMode 下会 panic)。
- 推荐在
TestMain里统一设:func TestMain(m *testing.M) { gin.SetMode(gin.TestMode); os.Exit(m.Run()) } - 如果没写
TestMain,每个测试函数开头加gin.SetMode(gin.TestMode),否则可能漏掉 -
gin.TestMode不影响路由注册和 handler 执行逻辑,只关闭调试中间件和美化日志 - 注意:一旦设了
gin.TestMode,就不能再用gin.DebugMode相关行为做断言,比如依赖gin.Mode切换配置的逻辑要单独 mock
c.Param() 返回空,其实只是忘了赋 c.Params;也有人 mock 了 service 却发现 DAO 层 SQL 没执行,其实是 gomock 没 mock 对接口层级。


















