You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Gin框架SetCookie未修改旧Cookie却新增,测试失败求助

问题:Gin Logout处理器测试中SetCookie新增同名Cookie导致测试失败

我正在测试Gin框架中包含ctx.SetCookie方法的Logout处理器:

func (a *authController) Logout(ctx *gin.Context) {
    refreshToken, err := ctx.Cookie("refresh_token")
    ...
    // 设置空值、MaxAge=-1来删除Cookie
    ctx.SetCookie("access_token", "", -1, "/", "localhost", false, true)
    ctx.SetCookie("refresh_token", "", -1, "/", "localhost", false, true)
    ctx.SetCookie("logged_in", "", -1, "/", "localhost", false, true)

    ctx.JSON(http.StatusOK, gin.H{"status": "success"})
}

测试函数代码如下:

recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.SetCookie("logged_in", "truee", 60*60, "/", "localhost", false, false)
req, _ := http.NewRequest("GET", "/logout", nil)
// 错误地用recorder设置请求Cookie
http.SetCookie(recorder, &http.Cookie{Name: "refresh_token", Value: "encodedRefreshToken", MaxAge: 60 * 60, Path: "/", Domain: "localhost", Secure: false, HttpOnly: true})
http.SetCookie(recorder, &http.Cookie{Name: "access_token", Value: "encodedAccessToken", MaxAge: 60 * 60, Path: "/", Domain: "localhost", Secure: false, HttpOnly: true})
req.Header = http.Header{"Cookie": recorder.Result().Header["Set-Cookie"]}
ctx.Request = req
test.mock()
authController.Logout(ctx)

调用后检查Cookie是否删除时,发现SetCookie并未修改原有Cookie,而是新增了同名Cookie,导致存在两对access_token等Cookie,测试不通过:

coockies := recorder.Result().Cookies()
for _, c := range coockies {
    if c.Name == "access_token" {
        assert.Equal(t, "", c.Value)
    }
    ...
}

错误原因

你误用了httptest.Recorder来构建请求的Cookie。httptest.Recorder的作用是记录服务器的响应,而非构造请求的Cookie。当你调用http.SetCookie(recorder, ...)时,实际上是在给响应的Set-Cookie头部添加内容,随后你把这些响应头当成请求的Cookie头传入,导致请求本身携带了这些Cookie;而Logout处理器又添加了删除Cookie的Set-Cookie指令,最终响应里会同时存在原有Cookie(来自错误的请求头构造)和删除指令的Cookie,出现重复。

另外,ctx.SetCookie("logged_in", ...)在测试上下文中的调用也不会作用到请求上——此时ctx的Request还未被赋值,这个操作完全无效。

解决方案

直接在http.Request的Header中正确设置Cookie,或者使用http.SetCookie给请求添加Cookie(注意是给请求的Header添加,而非给Recorder)。

修正后的测试代码:

import "strings"

// ...

recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
req, _ := http.NewRequest("GET", "/logout", nil)

// 构建请求的Cookie头部
var cookieSegments []string
// 添加access_token Cookie
accessCookie := &http.Cookie{Name: "access_token", Value: "encodedAccessToken", MaxAge: 60 * 60, Path: "/", Domain: "localhost", Secure: false, HttpOnly: true}
cookieSegments = append(cookieSegments, accessCookie.String())
// 添加refresh_token Cookie
refreshCookie := &http.Cookie{Name: "refresh_token", Value: "encodedRefreshToken", MaxAge: 60 * 60, Path: "/", Domain: "localhost", Secure: false, HttpOnly: true}
cookieSegments = append(cookieSegments, refreshCookie.String())
// 添加logged_in Cookie
loggedInCookie := &http.Cookie{Name: "logged_in", Value: "truee", MaxAge: 60 * 60, Path: "/", Domain: "localhost", Secure: false, HttpOnly: false}
cookieSegments = append(cookieSegments, loggedInCookie.String())

// 设置请求的Cookie头部
req.Header.Set("Cookie", strings.Join(cookieSegments, "; "))

ctx.Request = req
test.mock()
authController.Logout(ctx)

// 检查响应中的Cookie
cookies := recorder.Result().Cookies()
for _, c := range cookies {
    switch c.Name {
    case "access_token", "refresh_token", "logged_in":
        assert.Equal(t, "", c.Value)
        assert.Equal(t, -1, c.MaxAge)
    }
}

关键修正点

  1. 放弃用httptest.Recorder构造请求Cookie,直接操作http.Request的Header。
  2. 使用Cookie.String()方法将Cookie对象转换为符合HTTP Cookie头部格式的字符串,再用; 拼接多个Cookie。
  3. 测试时不仅检查Cookie的Value为空,还要验证MaxAge=-1——这是告诉浏览器删除Cookie的标准方式,比仅设置空值更可靠。

内容的提问来源于stack exchange,提问作者Omegon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 20:35:24