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

Go Gock 怎样只模拟指定主机的 HTTP 请求

来源:17golang原创

时间:2026-10-29 11:09:45 252浏览 收藏

如果测试里只想替换一个上游 API,而不希望其他域名也被 mock,关键是把主机写进 Gock 的请求声明,并让测试代码使用明确的 HTTP 客户端。Gock 的拦截器按主机、方法和路径匹配;未匹配请求在默认严格模式下返回错误,不会悄悄访问真实服务。

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

要点速览
  • gock.New("https://api.example.test") 将 mock 范围落到目标主机。
  • 自定义客户端要先调用 gock.InterceptClient(client),默认客户端则使用全局拦截。
  • 测试结束用 gock.Off(),自定义客户端再配合 gock.RestoreClient(client)。

先把 mock 范围锁定到一个主机

Gock 的声明式 API 先接收基础 URL,再继续追加方法和路径。下面的 mock 只接受 api.example.test 的 GET /v1/items,同一个客户端访问其他主机时不会命中这条声明。

package main

import (
    "fmt"
    "io"
    "net/http"

    "github.com/h2non/gock"
)

func main() {
    // 结束时清空 mock,避免影响同一进程中的后续测试。
    defer gock.Off()

    // 关闭主机正则解释,让示例域名按字面量参与匹配。
    gock.New("https://api.example.test").
        WithOptions(gock.Options{DisableRegexpHost: true}).
        Get("/v1/items").
        Reply(200).
        JSON(map[string]any{"items": []string{"go", "http"}})

    // 该请求的主机和路径都满足 mock 声明。
    resp, err := http.Get("https://api.example.test/v1/items")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close() // 读取后释放响应体。

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }
    fmt.Println(resp.StatusCode, string(body))
}

这里的主机是第一层筛选条件,路径是第二层筛选条件。即使把路径改成 /v1/users,也不会误用这条 /v1/items mock。DisableRegexpHost 适合希望域名按普通字符串匹配的场景;如果确实需要一组子域名,再考虑保留默认的正则主机匹配。

Go Gock 指定 api.example.test 主机经过 Host matcher 和 Path matcher 返回 mock response 的结构说明图
图1:Gock 指定主机匹配链路说明图,不是截图或运行证据。

自定义 http.Client 要显式接管

如果业务代码没有使用 http.DefaultClient,仅注册 mock 还不够。应在发送请求前对目标客户端调用一次 gock.InterceptClient,并让客户端保留自己的 Transport。

client := &http.Client{Transport: &http.Transport{}}

// 只把这个客户端交给 Gock,其他客户端不受本段测试影响。
gock.InterceptClient(client)

req, err := http.NewRequest(http.MethodGet, "https://api.example.test/v1/items", nil)
if err != nil {
    panic(err) // 请求地址构造失败时直接结束测试。
}
resp, err := client.Do(req)
if err != nil {
    panic(err) // 未匹配或传输失败都应让测试显式失败。
}
defer resp.Body.Close() // 自定义客户端也要关闭响应体。

默认客户端和自定义客户端的差别在“拦截入口”,不在主机匹配规则。官方文档建议同一个 http.Client 只拦截一次;多次包装会让清理和 Transport 关系变得难以追踪。

用 IsDone 判断目标请求是否真的命中

状态码为 200 只能说明拿到了响应,还应检查 mock 是否已经被消费。一次性 mock 可以用 gock.IsDone() 判断;如果它仍为 false,通常意味着主机、方法、路径或请求次数没有对上。

现象优先检查处理方向
返回未匹配错误scheme、host、path、method逐项对照 mock 声明
IsDone 为 false请求没有发送或次数不符检查请求流程与 Times/Persist
其他域名也被替换是否写了过宽的主机正则使用字面量主机或收窄表达式

严格拦截、真实网络和清理怎么选

默认模式更适合单元测试:只要请求不匹配,测试立即得到错误,能尽早发现上游地址被改错。若要让未匹配主机继续访问真实网络,可显式调用 gock.EnableNetworking(),但这会让测试依赖外部服务,应该只用于有意设计的集成场景。

// 测试完成后关闭 Gock 的拦截和真实联网开关。
defer gock.Off()
defer gock.RestoreClient(client)

// 只在明确需要“未匹配请求走真实网络”时开启。
gock.EnableNetworking()

更稳妥的做法是每个测试声明自己的目标主机、尽量使用独立客户端,并把清理写成 defer。这样即使中途断言失败,也不会把拦截器遗留给后续用例。

Go Gock 目标主机命中 mock、其他主机进入未匹配错误并在测试结束执行 Off 与 RestoreClient 的边界说明图
图2:目标主机、其他主机与清理动作的边界说明图,不是截图或运行证据。

常见问题

为什么写了 gock.New 仍然访问真实网络?

常见原因是自定义客户端没有调用 InterceptClient,或者实际请求的 scheme、主机、路径与声明不同。先关闭真实联网,再根据未匹配错误逐项比对。

主机名里的点号需要转义吗?

默认主机匹配支持正则语义;不需要正则时使用 DisableRegexpHost: true,可以把域名按字面量处理,减少点号等字符带来的误匹配。

什么时候使用 Persist?

同一 mock 需要被多次请求消费时才使用 Persist();单元测试通常更适合一次性声明,并用 IsDone() 检查调用次数。

因此,“只模拟指定主机”的最小组合就是:把主机写入 gock.New,为自定义客户端显式安装拦截器,默认保持严格未匹配策略,最后用 Off 和 RestoreClient 清理。主机范围收窄后,再按需要增加路径、请求头或参数匹配。

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