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 适合希望域名按普通字符串匹配的场景;如果确实需要一组子域名,再考虑保留默认的正则主机匹配。

自定义 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。这样即使中途断言失败,也不会把拦截器遗留给后续用例。

常见问题
为什么写了 gock.New 仍然访问真实网络?
常见原因是自定义客户端没有调用 InterceptClient,或者实际请求的 scheme、主机、路径与声明不同。先关闭真实联网,再根据未匹配错误逐项比对。
主机名里的点号需要转义吗?
默认主机匹配支持正则语义;不需要正则时使用 DisableRegexpHost: true,可以把域名按字面量处理,减少点号等字符带来的误匹配。
什么时候使用 Persist?
同一 mock 需要被多次请求消费时才使用 Persist();单元测试通常更适合一次性声明,并用 IsDone() 检查调用次数。
因此,“只模拟指定主机”的最小组合就是:把主机写入 gock.New,为自定义客户端显式安装拦截器,默认保持严格未匹配策略,最后用 Off 和 RestoreClient 清理。主机范围收窄后,再按需要增加路径、请求头或参数匹配。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
317 收藏
-
113 收藏
-
419 收藏
-
112 收藏
-
394 收藏
-
479 收藏
-
204 收藏
-
357 收藏
-
102 收藏
-
422 收藏
-
331 收藏
-
241 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习