Go语言net/http Transport接口封装及Mock单元测试实现问题
问题解决方案
你现有代码的核心问题是两个:一是init中初始化的默认http.Client没有绑定你定义的自定义Transport配置,二是HTTPClient接口的设计本身已经满足Mock需求,不需要额外调整,只要调整初始化和注入逻辑即可。
第一步:调整业务包初始化逻辑,支持自定义Transport配置
修改myhttp包代码,将Transport配置逻辑移到客户端初始化环节:
package myhttp import ( "crypto/tls" "net/http" "time" ) // HTTPClient 接口保留,仅要求实现Do方法,真实客户端和Mock客户端都可以兼容 type HTTPClient interface { Do(req *http.Request) (*http.Response, error) } var ( // 全局客户端实例 Client HTTPClient // 可自定义的配置项,也可以根据需求扩展 TLSInsecureSkipVerify bool IdleConnTimeout time.Duration = 10 * time.Second RequestTotalTimeout time.Duration = 30 * time.Second ) func init() { // 初始化时直接生成带自定义Transport的http.Client tlsCfg := tls.Config{InsecureSkipVerify: TLSInsecureSkipVerify} tr := &http.Transport{ IdleConnTimeout: IdleConnTimeout, TLSClientConfig: &tlsCfg, } Client = &http.Client{ Transport: tr, Timeout: RequestTotalTimeout, } } func Fetch(url string) (*http.Response, error) { req, err := http.NewRequest(http.MethodHead, url, nil) if err != nil { return nil, err } return Client.Do(req) }
第二步:Mock客户端兼容与优化
你现有Mock代码的核心逻辑是可用的,仅建议优化掉全局GetDoFunc避免并发测试冲突:
package httputils import "net/http" // MockClient 模拟HTTP客户端 type MockClient struct { DoFunc func(req *http.Request) (*http.Response, error) } // Do 实现HTTPClient接口 func (m *MockClient) Do(req *http.Request) (*http.Response, error) { if m.DoFunc != nil { return m.DoFunc(req) } // 默认返回可根据需求调整 return &http.Response{StatusCode: http.StatusOK}, nil }
第三步:单元测试使用示例
测试时直接替换全局Client为Mock实例即可,无需修改业务逻辑:
package myhttp_test import ( "net/http" "testing" "your/package/path/httputils" "your/package/path/myhttp" ) func TestFetch(t *testing.T) { // 备份原有客户端,测试结束后恢复避免影响其他用例 originClient := myhttp.Client defer func() { myhttp.Client = originClient }() // 初始化Mock客户端并替换全局实例 mockCli := &httputils.MockClient{} myhttp.Client = mockCli // 自定义当前测试用例的返回逻辑 mockCli.DoFunc = func(req *http.Request) (*http.Response, error) { // 可选:校验请求参数是否符合预期 if req.Method != http.MethodHead { t.Errorf("期望请求方法为HEAD,实际为%s", req.Method) } if req.URL.String() != "http://test.example" { t.Errorf("期望请求地址为http://test.example,实际为%s", req.URL.String()) } // 返回自定义响应 return &http.Response{ StatusCode: http.StatusOK, }, nil } // 执行业务逻辑测试 resp, err := myhttp.Fetch("http://test.example") if err != nil { t.Fatalf("未预期的错误: %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("期望响应状态码200,实际为%d", resp.StatusCode) } }
更推荐的无全局变量写法(可选)
如果要避免全局变量污染,更好的做法是用依赖注入的方式封装客户端:
package myhttp // Fetcher 封装HTTP请求逻辑 type Fetcher struct { client HTTPClient } // NewFetcher 生产环境初始化方法,传入自定义配置 func NewFetcher(tlsInsecureSkipVerify bool, idleTimeout time.Duration, totalTimeout time.Duration) *Fetcher { tlsCfg := tls.Config{InsecureSkipVerify: tlsInsecureSkipVerify} tr := &http.Transport{ IdleConnTimeout: idleTimeout, TLSClientConfig: &tlsCfg, } return &Fetcher{ client: &http.Client{ Transport: tr, Timeout: totalTimeout, }, } } // NewFetcherWithClient 测试用初始化方法,直接传入Mock客户端 func NewFetcherWithClient(client HTTPClient) *Fetcher { return &Fetcher{client: client} } func (f *Fetcher) Fetch(url string) (*http.Response, error) { req, err := http.NewRequest(http.MethodHead, url, nil) if err != nil { return nil, err } return f.client.Do(req) }
测试时直接传入Mock客户端即可,完全不需要修改全局变量。
内容的提问来源于stack exchange,提问作者Uwe Küllmar
相关产品推荐
相关产品推荐

