如何用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
相关产品推荐
相关产品推荐

