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

Go Gock 怎样给同一路径设置多次不同响应

来源:17golang原创

时间:2026-10-29 11:14:35 412浏览 收藏

我第一次给 Gock 写重试测试时,最容易卡住的地方不是响应体,而是“同一个路径为什么第二次请求还拿到第一次的结果”。答案是:可以为完全相同的 HTTP 方法和路径注册多条规则,Gock 会按模拟规则进入池子的 FIFO 声明顺序消费它们;每条规则默认只匹配一次,也可以用 Times(n) 明确保留次数。

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

同一路径多次返回不同响应的关键写法,是连续声明相同匹配条件、为每条规则设置清晰的 Times 次数,并让请求按预期顺序消费规则。

为什么同一路径可以返回不同结果

Gock 的匹配不是“相同路径永远绑定一个响应”。官方 README 说明,HTTP 请求会从已声明的模拟期望池中按 FIFO 顺序匹配;请求匹配成功后,当前规则被消费,下一次相同请求就有机会使用下一条规则。因此,第一次可以模拟缓存未命中,第二次可以模拟服务恢复,路径本身不需要改成两个地址。

这里有两个容易混在一起的概念:规则的匹配条件和规则的响应内容。前者可以相同,后者可以不同。只要请求方法、主机、路径及你额外声明的查询参数或请求体都相同,Gock 就会按声明顺序处理这些规则。

同一请求条件进入 Gock FIFO 规则池并关联两条不同响应定义的静态结构图
图1:同一路径多条模拟规则的 FIFO 结构说明图,不表示真实运行截图。

用 Times 给连续请求安排不同响应

最小写法是连续写两条相同的 Get 规则,并分别构造响应。示例中第一条返回 503,第二条返回 200;两条都使用 Times(1),这样意图比依赖默认值更清楚。代码只是文章示例,未在本机执行。

package retrytest

import (
    "encoding/json"
    "net/http"
    "testing"

    "github.com/h2non/gock"
)

func TestRetryGetsDifferentResponses(t *testing.T) {
    // 关闭真实网络,避免规则写错时把请求发到外部服务。
    gock.DisableNetworking()
    gock.OffAll()
    t.Cleanup(gock.OffAll)

    // 两条规则的请求条件完全相同,声明顺序决定先后消费关系。
    gock.New("https://api.example.test").
        Get("/v1/profile").
        Times(1).
        Reply(http.StatusServiceUnavailable).
        JSON(map[string]string{"state": "retry"})

    gock.New("https://api.example.test").
        Get("/v1/profile").
        Times(1).
        Reply(http.StatusOK).
        JSON(map[string]string{"state": "ready"})

    for i, wantStatus := range []int{http.StatusServiceUnavailable, http.StatusOK} {
        // 每一轮都访问同一个方法和路径,模拟第一次失败、第二次恢复。
        resp, err := http.Get("https://api.example.test/v1/profile")
        if err != nil {
            t.Fatalf("第 %d 次请求失败: %v", i+1, err)
        }
        var body map[string]string
        if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
            resp.Body.Close() // 解码失败时也要释放响应体。
            t.Fatalf("第 %d 次响应不是有效 JSON: %v", i+1, err)
        }
        resp.Body.Close() // 读取完成后关闭模拟响应体。
        if resp.StatusCode != wantStatus {
            t.Fatalf("第 %d 次状态码=%d,期望=%d", i+1, resp.StatusCode, wantStatus)
        }
    }

    // 两条 Times(1) 规则都已消费,IsDone 可作为规则耗尽的辅助判断。
    if !gock.IsDone() {
        t.Fatal("仍有未消费的 Gock 规则")
    }
}

如果第一次请求返回 200,通常先检查规则声明顺序和是否有其他更宽泛的规则抢先匹配,而不是先修改 JSON。Gock 的文档还建议把更具体的规则放在更宽泛的规则之前,避免通用匹配条件提前截获请求。

连续响应排查时要看哪几层

排查层要确认的内容常见修复
请求条件方法、主机、路径和参数是否真的相同把额外的 MatchParam 或请求体条件逐项对齐
规则顺序是否有通用规则在前面先声明更具体的规则,再声明通用规则
次数控制Times(n) 是否覆盖实际调用次数按调用契约设置次数,不用无限 Persist 冒充重试次数
响应清理每轮响应体是否关闭解码后关闭 Body,错误路径也释放资源

“同一路径不同响应”只解决规则层的顺序,不会自动替你实现重试循环。真实代码是否会发出第二次请求,仍由调用方的重试策略决定;如果只发出一次请求,第二条规则会保持未消费状态。

Times、IsDone 和 OffAll 不是一回事

Times(n) 是单条请求模拟的剩余次数设置;IsDone() 用来辅助判断当前注册的模拟是否都已完成;OffAll() 则负责关闭默认拦截、清空规则,并清理未匹配请求记录。测试结束时用 t.Cleanup 调用清理函数,可以避免前一个场景的规则影响后续测试。

Gock Times次数、规则计数、IsDone、未匹配记录和OffAll清理职责的静态结构图
图2:次数控制与全局清理职责说明图,不表示真实运行结果。

如果你需要同一个响应持续服务多次,可以考虑 Persist();但它表达的是长期有效规则,不再是“第一次失败、第二次成功”这种有界序列。序列测试更适合给每个响应单独写规则,并用 Times(1) 把意图固定下来。

写重试模拟前的检查清单

先确认所有规则的请求条件是否足够具体,再确认声明顺序是否和期望的响应序列一致;然后用 Times 显式写出每条规则允许消费的次数。最后检查真实请求是否真的会重试、响应体是否关闭,以及测试结束时是否清理 Gock 状态。

同一路径的两条规则可以都不写 Times 吗?

可以,Gock 新建规则的默认计数是一次;不过写出 Times(1) 更容易表达序列意图,也便于读者检查调用次数。

第二条规则没有被消费说明什么?

通常说明调用方没有发出第二次请求,或者第一条规则没有按预期被消费。先看实际请求次数和匹配条件,再判断是否需要调整重试逻辑。

能不能用 Persist 返回多个不同响应?

不适合。Persist 让同一条规则持续有效,不能表达有限的响应序列;要模拟不同阶段的结果,应使用多条同条件规则和明确的次数。

参考:Gock 官方 README、Gock Request API。

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