登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go Gock Persist 模拟为什么会影响后续测试

来源:17golang原创

时间:2026-10-29 13:32:13 428浏览 收藏

会影响后续测试的根因很直接:Gock 的 Persist() 会把当前 HTTP mock 标记为持久状态,命中后不会像默认 mock 那样因为计数归零而结束。因此它仍然留在 Gock 的全局 mock 集合里,后续发往相同方法和地址的请求还有机会继续命中旧响应。测试代码里只要把 Persist 当成“多调用几次”的快捷写法,却没有在测试边界清理,串测就很容易发生。

官方地址:https://github.com/h2non/gock

这篇只讨论 Persist 的生命周期和测试隔离,不把它扩展成 Gock 入门。结论可以先记住:需要有限次数时用 Times(n),需要跨多个请求复用时才用 Persist(),而每个测试都应明确安排 gock.Off()。

Persist 改变的是 mock 的结束条件

Gock 默认创建的请求 mock 初始计数为 1。请求匹配成功后,普通 mock 会递减计数;计数归零时,mock 会被标记为不可用,清理过程可以把它从集合中移除。Persist() 则把请求对象的持久标志设为真,命中时不再依赖计数归零来结束,等于把“这次命中后是否还存在”的决定权从计数策略改成了显式清理。

Gock Persist 持久 mock 与计数和清理对象之间的关系说明图
图1:Persist 改变 mock 结束条件的静态结构说明图,不是运行截图或运行证据。

这也是它会影响后续测试的原因:Gock 的 mock 注册表是包级共享状态。第一个测试结束后,如果 Persist mock 仍在注册表中,第二个测试不需要重新声明它,也可能收到同一个状态码和响应体。响应内容看起来“正确”,反而会让问题更难发现。

三种写法对应三种测试意图

不要用 Persist 代替所有“重复请求”场景。它和 Times、默认 mock 的边界不同:

写法命中后的状态适合场景
默认写法通常只消费一次验证一次请求及其响应
Times(2)明确保留两次命中重试、分页或固定次数调用
Persist()不因命中次数自动结束同一测试内的长期公共 stub,或明确的持续模拟

例如,重试逻辑更适合写成有限次数,而不是让 mock 无限存在:

mock := gock.New("https://api.example.test").
    Get("/profile").
    Times(2). // 中文注释:只允许重试场景消费两次
    Reply(200).
    JSON(map[string]string{"name": "demo"})

_ = mock // 中文注释:实际测试中继续调用被测客户端,并断言两次请求的结果

如果只是想检查 mock 是否全部被消费,可以在测试末尾读取 gock.IsDone();但它不能替代清理。Persist mock 没有自然结束点,不能把“IsDone 为真”当成全局状态已经恢复。

Gock 默认命中、Times 次数限制与 Persist 持久状态的关系说明图
图2:三种 mock 生命周期的静态对照图,帮助选择有限次数或持久状态,不是运行截图。

把清理动作放在测试边界

最稳妥的写法是让每个使用 Gock 的测试自己负责收口,而不是依赖测试文件中某个全局初始化函数:

func TestProfile(t *testing.T) {
    defer gock.Off() // 中文注释:测试结束时清空本测试注册的 mock,避免污染后续用例

    gock.New("https://api.example.test").
        Get("/profile").
        Persist(). // 中文注释:只有本测试明确需要重复命中时才保留持久状态
        Reply(200).
        JSON(map[string]string{"name": "demo"})

    // 中文注释:这里调用被测函数,可以连续发送多个相同请求
    // got, err := loadProfile(context.Background())
    // if err != nil { t.Fatal(err) }
}

func TestCustomClient(t *testing.T) {
    client := &http.Client{Transport: &http.Transport{}}
    gock.InterceptClient(client)
    defer gock.RestoreClient(client) // 中文注释:恢复自定义客户端的 RoundTripper
    defer gock.Off()                 // 中文注释:同时清空全局 mock 注册表

    // 中文注释:声明具体请求后再执行被测调用,减少匹配顺序歧义
}

自定义客户端被拦截时,Off() 负责 mock 集合,RestoreClient() 负责客户端传输器,二者是不同层面的清理。只调用其中一个,仍可能留下另一类状态。

后续测试异常时先查这四件事

  1. 搜索测试和公共初始化代码里是否出现了 Persist(),确认它是不是本用例真正需要的。
  2. 检查是否在测试结束时调用了 gock.Off(),尤其是测试中途 t.Fatal 的路径。
  3. 比较具体 mock 与通用 mock 的声明顺序;Gock 按声明顺序匹配,持久的通用规则可能遮住后续具体规则。
  4. 若使用自定义 http.Client,确认测试结束时也执行了 gock.RestoreClient(client)。

所以,Persist 本身不是错误,它只是把 mock 的生命周期交给调用者管理。测试只要把“允许重复命中”和“何时清空状态”写成两个独立决定,后续用例就不会莫名其妙继承前一个用例的 HTTP 响应。

常见问题

Persist 和 Times 可以一起使用吗?

从测试意图上不建议这样写。Persist 的语义是命中后持续存在,而 Times 的语义是按次数结束;如果需要明确上限,直接使用 Times 更容易读懂和维护。

调用 IsDone 能发现 Persist 残留吗?

不能把它当成残留检测器。Persist mock 可能持续处于可匹配状态,IsDone 只适合辅助判断当前 mock 是否按预期消费完;真正的隔离动作仍然是 Off,客户端拦截还要配合 RestoreClient。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>