应使用 gin.createtestcontext + httptest.newrequest 构造测试上下文,因 http.servehttp 跳过 gin 生命周期,导致 c.param、c.shouldbindjson 等失效;需手动设置 c.params、req.header、rawquery 等,并启用 gin.testmode。

直接用 gin.CreateTestContext + httptest.NewRequest 构造测试上下文,而不是调 router.Run() 或 http.ServeHTTP —— 后者绕过中间件、路由匹配和上下文初始化,c.Param、c.ShouldBindJSON 全部失效。
为什么不能用 http.ServeHTTP 测 Gin handler
http.ServeHTTP 是底层 HTTP 处理入口,它跳过了 Gin 自己的请求生命周期:不执行中间件链、不解析 :id 路径参数、不初始化 c.Keys 和 c.Errors,甚至 c.Request 都是裸指针。结果就是:c.Param("id") 返回空字符串,c.ShouldBindJSON panic,c.GetHeader("Authorization") 拿不到值。
正确路径是:gin.CreateTestContext 创建带完整 Gin 上下文的 *gin.Context,再把 httptest.NewRequest 注入进去:
- 必须手动设
req.Header.Set("Content-Type", "application/json"),否则c.ShouldBindJSON直接返回err - 路径参数要显式赋值:
c.Params = gin.Params{{Key: "id", Value: "123"}} - Query 参数写进
req.URL.RawQuery = "page=2"或拼在路径后 - 别忘了
gin.SetMode(gin.TestMode),否则日志刷屏、panic 被 Recovery 捕获掩盖真实错误
sqlmock 和 gomock 怎么分工
sqlmock 拦截 *sql.DB 的 Query/Exec 调用,专用于 DAO 层;gomock 生成接口 mock 实例,用于 service 层隔离 repository 依赖。两者不是替代关系,而是分层协作。
-
sqlmock场景:验证 SQL 是否拼对、参数是否传准、是否执行了预期语句(比如没漏WHERE) -
gomock场景:service 层调用UserRepo.GetUserByID,你不想走真实 DB,就 mock 这个接口返回固定用户或模拟 error - 坑点:
gomock只能 mock 接口,不能 mock 结构体方法;接口必须导出(首字母大写),且定义在被测包外(如internal/repo/user.go),否则mockgen找不到 - 调试时可临时用
sqlmock.New(sqlmock.QueryMatcherEqual)关闭严格模式,避免未命中的 query 导致 panic
中间件怎么测才不漏逻辑
中间件本质是 func(*gin.Context),既可单独单元测状态变更(如是否 c.Abort()、是否 c.Set("user", u)),也可集成进 router 测行为影响(如鉴权失败是否返回 401)。
- 单元测中间件:构造 mock
*gin.Context,调用中间件函数,断言c.IsAborted()、c.Get("user")、c.Writer.Status() - 集成测中间件:注册到
gin.Engine,发真实请求(httptest.NewRequest),重点验证非法 token 是否被拦截、合法 token 是否透传下游 - 常见漏测点:没设
req.Header.Set("Authorization", "Bearer xxx"),结果中间件根本没触发;或用了gin.Default()却没关掉 Logger/Recovery,导致响应体混入日志文本 - JWT 类中间件,测试用固定签名 key + 预签 token,别连真实密钥服务
c.BindJSON 和 c.ShouldBindJSON 别混用
c.BindJSON 是“自动响应型”:解析失败直接写 400 并中断 handler;c.ShouldBindJSON 是“纯校验型”:只返回 error,由你决定怎么处理——单元测试必须用后者,否则你根本拿不到 error 做断言。
- handler 里优先写
if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": "bad request"}) } - 测试时主动构造非法 JSON(如
{"id": "abc"}但 id 字段是 int),断言w.Code == 400且响应体含 error 字段 - 如果非要用
BindJSON,测试断言里必须检查w.Code,不能只看w.Body.String()—— 很可能 body 是空但 status 是 400 - 别依赖
gin.Default()的默认中间件,它们会干扰断言;测试 router 应该用gin.New()显式注册所需中间件
最常被忽略的是上下文初始化细节:c.Params、c.Request、c.Keys 全都要手动设,Gin 不会自动从 httptest.NewRequest 推导出来。少设一个,c.Param 就为空,c.MustGet 就 panic。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











