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

如何用httptest对HttpRequest组件的请求重试逻辑做单元测试?

解决方案:通过依赖注入Mock HTTP客户端模拟重试场景

要测试首次连接拒绝、第二次请求成功的场景,最可靠的方式是替换HttpRequest组件的HTTP客户端,用自定义的RoundTripper精准控制每次请求的返回结果,完全不需要依赖真实的网络端口或服务器启停时机。

步骤1:确保组件支持注入自定义HTTP客户端

先修改HttpRequestConfig结构体,添加Client字段(如果还没有的话),允许外部传入自定义http.Client:

type HttpRequestConfig struct {
    URL     string
    Method  string
    Timeout time.Duration
    Retry   int
    Client  *http.Client // 新增:允许注入自定义客户端
    // 其他业务字段...
}

在HttpRequest组件内部,优先使用配置中的客户端,兜底用默认客户端:

func HttpRequest[T any](config HttpRequestConfig) (T, error) {
    client := config.Client
    if client == nil {
        client = http.DefaultClient
    }
    // 原有请求、重试、反序列化逻辑...
}

步骤2:编写Mock RoundTripper模拟请求结果

实现一个MockRoundTripper,记录调用次数,第一次返回"connection refused"错误,第二次返回成功响应:

import (
    "io"
    "net"
    "net/http"
    "strings"
    "syscall"
)

type MockRoundTripper struct {
    callCount   int
    successResp *http.Response
}

func (m *MockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
    m.callCount++
    // 第一次请求返回连接拒绝错误
    if m.callCount == 1 {
        return nil, &net.OpError{
            Op:   "dial",
            Net:  "tcp",
            Addr: &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 8080},
            Err:  syscall.ECONNREFUSED,
        }
    }
    // 第二次请求返回成功响应
    return m.successResp, nil
}

步骤3:编写单元测试

在测试中构造Mock客户端并注入配置,调用一次组件即可验证重试逻辑:

import (
    "testing"
    "time"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func TestHttpRequest_RetryOnConnRefused(t *testing.T) {
    // 构造成功的响应体
    userJSON := `{"id":1,"name":"test"}`
    successResp := &http.Response{
        StatusCode: http.StatusOK,
        Body:       io.NopCloser(strings.NewReader(userJSON)),
        Header:     make(http.Header),
    }

    // 创建Mock传输器和客户端
    mockRT := &MockRoundTripper{successResp: successResp}
    mockClient := &http.Client{
        Transport: mockRT,
        Timeout:   1 * time.Second,
    }

    // 构造请求配置
    config := HttpRequestConfig{
        URL:    "http://localhost:8080/user", // URL可任意填写,Mock会拦截所有请求
        Method: http.MethodGet,
        Retry:  1, // 重试1次,总共发起2次请求
        Client: mockClient,
    }

    // 调用组件
    user, err := HttpRequest[User](config)
    if err != nil {
        t.Fatalf("HttpRequest failed unexpectedly: %v", err)
    }

    // 验证返回结果正确
    if user.ID != 1 || user.Name != "test" {
        t.Errorf("Expected user {ID:1, Name:\"test\"}, got %+v", user)
    }

    // 验证确实触发了2次请求
    if mockRT.callCount != 2 {
        t.Errorf("Expected 2 request attempts, got %d", mockRT.callCount)
    }
}

方案优势

  • 无竞态条件:完全在内存中模拟请求,不需要依赖网络端口或服务器启停时机,测试结果稳定。
  • 精准可控:可灵活调整每次请求的返回值,还能拓展测试超时、5xx状态码等其他重试场景。
  • 代码更健壮:通过依赖注入解耦组件与HTTP客户端,提升了代码的可测试性和扩展性。

内容的提问来源于stack exchange,提问作者Andrey

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 01:02:49