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

如何对配置Microsoft.Extensions.Http.Resilience的类型化HttpClient做单元测试

测试带有Microsoft.Extensions.Http.Resilience弹性策略的HttpClient客户端

你的问题核心是:AddStandardResilienceHandler是IHttpClientBuilder的扩展方法,并非ResiliencePipelineBuilder的成员,所以直接用后者找不到这个方法。下面提供两种可行的测试方案:

方案一:用依赖注入容器构建客户端(最贴近生产配置)

这种方式直接复用项目里的DI配置逻辑,能拿到和生产环境完全一致的、带标准弹性策略的客户端实例,还能方便模拟Http响应:

[Fact]
public async Task MyClient_WithStandardResilience_WorksAsExpected()
{
    // 构建测试用DI容器
    using var host = Host.CreateDefaultBuilder()
        .ConfigureServices(services =>
        {
            // 完全复用生产环境的客户端配置
            services.AddHttpClient<IMyClient, MyClient>()
                .AddStandardResilienceHandler();

            // 替换HttpMessageHandler来模拟接口响应(避免真实请求)
            services.PostConfigure<HttpClientFactoryOptions>(nameof(MyClient), options =>
            {
                options.HttpMessageHandlerBuilderActions.Add(builder =>
                {
                    var mockHandler = new Mock<HttpMessageHandler>();
                    mockHandler.Protected()
                        .Setup<Task<HttpResponseMessage>>(
                            "SendAsync",
                            ItExpr.IsAny<HttpRequestMessage>(),
                            ItExpr.IsAny<CancellationToken>())
                        .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)
                        {
                            Content = new StringContent("{\"data\": 123}")
                        });
                    builder.PrimaryHandler = mockHandler.Object;
                });
            });
        })
        .Build();

    // 从容器中获取配置好的客户端
    var sut = host.Services.GetRequiredService<IMyClient>();

    // 执行测试
    var result = await sut.GetData();

    // 断言结果
    Assert.Equal(123, result);
}

方案二:手动构建等价的弹性策略管道

AddStandardResilienceHandler本质是封装了一组默认的Http弹性策略(重试、断路器、超时、舱壁、降级),你可以手动构建这组策略的管道,包裹客户端的Http请求:

[Fact]
public async Task MyClient_WithManualResiliencePipeline_BehavesCorrectly()
{
    // 模拟HttpClient的响应
    var mockHandler = new Mock<HttpMessageHandler>();
    mockHandler.Protected()
        .Setup<Task<HttpResponseMessage>>(
            "SendAsync",
            ItExpr.IsAny<HttpRequestMessage>(),
            ItExpr.IsAny<CancellationToken>())
        // 可以模拟多次失败再成功,用来验证重试逻辑
        .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError))
        .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent("{\"data\": 123}")
        });

    var httpClient = new HttpClient(mockHandler.Object);
    var sut = new MyClient(httpClient);

    // 构建和AddStandardResilienceHandler等价的弹性管道
    var pipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
        .AddRetry(new HttpRetryStrategyOptions())
        .AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions())
        .AddTimeout(new HttpTimeoutStrategyOptions())
        .AddRateLimiter(new HttpRateLimiterStrategyOptions())
        .AddFallback(new HttpFallbackStrategyOptions())
        .Build();

    // 通过管道执行Http请求(如果MyClient的GetData内部调用了HttpClient,也可以直接包裹该方法)
    var response = await pipeline.ExecuteAsync(async token =>
    {
        return await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/data"), token);
    });

    // 解析响应并断言
    var data = await response.Content.ReadFromJsonAsync<DataModel>();
    Assert.Equal(123, data.Data);

    // 验证重试逻辑:如果模拟了一次失败一次成功,这里断言SendAsync被调用2次
    mockHandler.Protected().Verify(
        "SendAsync",
        Times.Exactly(2),
        ItExpr.IsAny<HttpRequestMessage>(),
        ItExpr.IsAny<CancellationToken>());
}

注意事项

  • 方案一更推荐,因为能保证测试和生产环境的策略配置完全一致,避免出现测试通过但生产出问题的情况
  • 如果要验证弹性策略的具体行为(比如重试次数、断路器触发条件),可以通过模拟Http响应的失败次数,结合Mock的调用次数断言来实现

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 21:00:19