You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 13:24:05