如何使用Go语言HttpMock库进行无网络依赖的Mock测试编写?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1012个文字,预计阅读时间需要5分钟。
由于测试要求快速、可重复、不依赖外部状态,以下是对伪原创内容的简化
因测试需快速、可重复、独立于环境,故采用http.Server。该结构易于端口冲突、泄露,故需注意。
- 真实服务启动慢,
net.Listen可能失败,server.Close()忘关会导致后续测试失败 - 一旦测试中调用
http.Get("https://api.example.com"),就已脱离可控范围 - 多数 HTTP client 库(包括标准库)都支持传入自定义
http.Transport,这才是 Mock 入口
用 httpmock 库拦截请求比手写 RoundTripper 更稳
httpmock 不是“启动 mock server”,而是注册一个假的 RoundTripper,在请求发出前就截住它。它对标准库透明,不需要改业务代码里的 http.Client 初始化逻辑——只要确保测试前启用、测试后清理即可。
- 安装:
go get -u github.com/jarcoal/httpmock - 启用必须在测试函数开头:
httpmock.Activate();否则所有请求照常走网络 - 必须在
defer httpmock.DeactivateAndReset(),否则会影响其他测试用例 - 匹配路径时注意:默认区分 query 参数顺序,
/api/users?id=1&name=a和/api/users?name=a&id=1是两个不同 pattern
func TestFetchUser(t *testing.T) { httpmock.Activate() defer httpmock.DeactivateAndReset() httpmock.RegisterResponder("GET", "https://api.example.com/user/123", httpmock.NewStringResponder(200, `{"id":123,"name":"alice"}`)) resp, err := http.Get("https://api.example.com/user/123") // ... 断言 resp.Body }
遇到 Get https://...: dial tcp: lookup... 错误说明没激活或匹配失败
这个错误不是 DNS 问题,是 httpmock 没生效的明确信号。常见原因就三个:没调 httpmock.Activate()、注册的 URL 和实际发的 URL 字符串不完全一致、用了 HTTPS 但注册的是 HTTP(或反之)。
- 检查 URL 协议是否严格匹配:
https://和http://是不同 scheme,不能混用 - 路径末尾斜杠敏感:
/user≠/user/,建议统一用httpmock.RegisterRegexpResponder配正则 - 如果业务代码里用了自定义
http.Client并指定了Transport,那httpmock默认不接管——得手动把httpmock.GetTransport()塞进去 - 某些库(如
resty)底层也走http.Client,同样适用,但要注意其内部是否缓存了 client 实例
不用第三方库时,最简 RoundTripper Mock 写法
如果项目禁止引入新依赖,或者只想测一两个固定请求,直接实现 http.RoundTripper 接口更轻量。重点在于:不碰网络、不启 goroutine、不阻塞。
立即学习“go语言免费学习笔记(深入)”;
- 返回的
*http.Response必须带Body字段,且是io.ReadCloser类型,别直接传字符串 - 别忘了设置
ContentLength,否则某些 client 会卡在读 body - 不要在
RoundTrip方法里做耗时操作,比如解析 JSON 或 sleep——这会让测试变慢且不可靠
type mockTransport struct{}
func (m *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
body := ioutil.NopCloser(strings.NewReader(`{"ok":true}`))
return &http.Response{
StatusCode: 200,
Body: body,
Header: make(http.Header),
Request: req,
}, nil
}
// 使用:
client := &http.Client{Transport: &mockTransport{}}
HTTP Mock 的关键不在“像不像服务”,而在“能不能精准控制输入输出”。很多人卡在 URL 字符串拼写、协议头大小写、query 参数顺序这些细节上,而不是逻辑本身。多打两行日志输出实际请求的 req.URL.String(),比猜半天快得多。
本文共计1012个文字,预计阅读时间需要5分钟。
由于测试要求快速、可重复、不依赖外部状态,以下是对伪原创内容的简化
因测试需快速、可重复、独立于环境,故采用http.Server。该结构易于端口冲突、泄露,故需注意。
- 真实服务启动慢,
net.Listen可能失败,server.Close()忘关会导致后续测试失败 - 一旦测试中调用
http.Get("https://api.example.com"),就已脱离可控范围 - 多数 HTTP client 库(包括标准库)都支持传入自定义
http.Transport,这才是 Mock 入口
用 httpmock 库拦截请求比手写 RoundTripper 更稳
httpmock 不是“启动 mock server”,而是注册一个假的 RoundTripper,在请求发出前就截住它。它对标准库透明,不需要改业务代码里的 http.Client 初始化逻辑——只要确保测试前启用、测试后清理即可。
- 安装:
go get -u github.com/jarcoal/httpmock - 启用必须在测试函数开头:
httpmock.Activate();否则所有请求照常走网络 - 必须在
defer httpmock.DeactivateAndReset(),否则会影响其他测试用例 - 匹配路径时注意:默认区分 query 参数顺序,
/api/users?id=1&name=a和/api/users?name=a&id=1是两个不同 pattern
func TestFetchUser(t *testing.T) { httpmock.Activate() defer httpmock.DeactivateAndReset() httpmock.RegisterResponder("GET", "https://api.example.com/user/123", httpmock.NewStringResponder(200, `{"id":123,"name":"alice"}`)) resp, err := http.Get("https://api.example.com/user/123") // ... 断言 resp.Body }
遇到 Get https://...: dial tcp: lookup... 错误说明没激活或匹配失败
这个错误不是 DNS 问题,是 httpmock 没生效的明确信号。常见原因就三个:没调 httpmock.Activate()、注册的 URL 和实际发的 URL 字符串不完全一致、用了 HTTPS 但注册的是 HTTP(或反之)。
- 检查 URL 协议是否严格匹配:
https://和http://是不同 scheme,不能混用 - 路径末尾斜杠敏感:
/user≠/user/,建议统一用httpmock.RegisterRegexpResponder配正则 - 如果业务代码里用了自定义
http.Client并指定了Transport,那httpmock默认不接管——得手动把httpmock.GetTransport()塞进去 - 某些库(如
resty)底层也走http.Client,同样适用,但要注意其内部是否缓存了 client 实例
不用第三方库时,最简 RoundTripper Mock 写法
如果项目禁止引入新依赖,或者只想测一两个固定请求,直接实现 http.RoundTripper 接口更轻量。重点在于:不碰网络、不启 goroutine、不阻塞。
立即学习“go语言免费学习笔记(深入)”;
- 返回的
*http.Response必须带Body字段,且是io.ReadCloser类型,别直接传字符串 - 别忘了设置
ContentLength,否则某些 client 会卡在读 body - 不要在
RoundTrip方法里做耗时操作,比如解析 JSON 或 sleep——这会让测试变慢且不可靠
type mockTransport struct{}
func (m *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
body := ioutil.NopCloser(strings.NewReader(`{"ok":true}`))
return &http.Response{
StatusCode: 200,
Body: body,
Header: make(http.Header),
Request: req,
}, nil
}
// 使用:
client := &http.Client{Transport: &mockTransport{}}
HTTP Mock 的关键不在“像不像服务”,而在“能不能精准控制输入输出”。很多人卡在 URL 字符串拼写、协议头大小写、query 参数顺序这些细节上,而不是逻辑本身。多打两行日志输出实际请求的 req.URL.String(),比猜半天快得多。

