我正在努力为一个名为HttpRequest的组件编写单元测试,该组件包装HTTP请求并处理响应解组.最近,我向该组件添加了一个功能,允许它在第一次try 时遇到"连接被拒绝"错误时重试HTTP请求.

要使用HttpRequest组件,我将其命名为:user, err := HttpRequest[User](config).配置参数包含执行请求所需的所有信息,如URL、方法、超时、重试次数和请求正文.它还将响应正文解组为指定类型的实例(在本例中为User)

当我try 测试初始请求失败并出现"连接被拒绝"错误,但第二次try 成功的场景时,问题就出现了.重试发生在组件内部,因此我只对该组件进行一次调用.

我发现为这种场景创建单元测试很有挑战性,因为为了使请求失败并显示"连接被拒绝",被调用的端口上不需要有侦听器.问题是,当使用httptest时,它总是在创建实例时侦听端口,即使使用httptest.NewUnstartedServer也是如此.因此,在创建httptest实例后,我的客户端代码中不会出现"拒绝连接"的错误.

然而,在创建httptest实例之前,我不知道它将监听哪个端口.httptest总是 Select 一个随机端口,并且无法以编程方式指定一个端口.这意味着我不能在创建httptest实例之前调用HttpRequest.

对于如何有效地对这样的场景进行单元测试,有人有什么 idea 吗?

推荐答案

NewUnstartedServer就是这么简单:

func NewUnstartedServer(handler http.Handler) *Server {
    return &Server{
        Listener: newLocalListener(),
        Config:   &http.Server{Handler: handler},
    }
}

如果您自己 Select 一个端口有效,您可以这样做:

func MyNewUnstartedServer(port int, handler http.Handler) *httptest.Server {
    addr := fmt.Sprintf("127.0.0.1:%d", port)
    l, err := net.Listen("tcp", addr)
    if err != nil {
        addr = fmt.Sprintf("[::1]::%d", port)
        if l, err = net.Listen("tcp6", addr); err != nil {
            panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err))
        }
    }
    return &httptest.Server{
        Listener: l,
        Config:   &http.Server{Handler: handler},
    }
}

创建监听程序的代码从httptest.newLocalListener修改.


另一种 Select 是实现http.RoundTripper接口,并使用此往返程序创建http.Client.下面是一个抄袭自net/http/client_test.go的例子:

type recordingTransport struct {
    req *Request
}

func (t *recordingTransport) RoundTrip(req *Request) (resp *Response, err error) {
    t.req = req
    return nil, errors.New("dummy impl")
}

func TestGetRequestFormat(t *testing.T) {
    setParallel(t)
    defer afterTest(t)
    tr := &recordingTransport{}
    client := &Client{Transport: tr}
    url := "http://dummy.faketld/"
    client.Get(url) // Note: doesn't hit network
    if tr.req.Method != "GET" {
        t.Errorf("expected method %q; got %q", "GET", tr.req.Method)
    }
    if tr.req.URL.String() != url {
        t.Errorf("expected URL %q; got %q", url, tr.req.URL.String())
    }
    if tr.req.Header == nil {
        t.Errorf("expected non-nil request Header")
    }
}

Go相关问答推荐

CGO如何转换为文件*类型

我不能让GIO画一个按钮

为什么我不能使用Docker从本地访问我的Gin应用程序?

如何将GoFr筛选器用于查询参数?

在Go中旋转矩阵

如果第一次匹配条件,如何跳过切片中的值

在 Go sync.Map 中,为什么这部分实现不一致或者我误解了什么?

替换字符串中的最后一个字符

在两个单独的速率受限端点之间同步请求

如何在模板中传递和访问 struct 片段和 struct

获取不带类型参数的泛型 struct 的类型名称

如何获取多个 url 参数值

Go 中 SDL Surface 的 OpenGL 纹理

如何从 Go 1.18 中的单个方法返回两种不同的具体类型?

通用函数与外部包中的常见成员一起处理不同的 struct ?

为什么 Go 中的 maps.Keys() 将 map 类型指定为 M?

手动将 OpenTelemetry 上下文从 golang 提取到字符串中?

Go:如何通过 GIN-Router 从 AWS S3 将文件作为二进制流发送到浏览器?

获取单调时间,同 CLOCK_MONOTONIC

Go 错误:cannot use generic type without instantiation