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

如何为单元测试基于依赖注入实现IHttpClientFactory?

HttpClient接口适配与单元测试方案

.NET官方未提供原生的IHttpClient抽象接口,我们可以通过适配器模式包装原生HttpClient实现自定义接口,同时直接复用.NET原生的IHttpClientFactory做实例管理,不需要自己实现工厂逻辑,即可满足依赖注入与单元测试需求。


1. 定义适配接口

按需定义你业务中需要用到的HttpClient核心方法,覆盖常用的请求场景即可:

public interface IHttpClient
{
    Task<HttpResponseMessage> GetAsync(string requestUri, CancellationToken cancellationToken = default);
    Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken = default);
    Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content, CancellationToken cancellationToken = default);
    Task<HttpResponseMessage> DeleteAsync(string requestUri, CancellationToken cancellationToken = default);
    Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default);
    // 其他用到的方法比如GetStringAsync、GetStreamAsync等可以按需补充
}

2. 实现适配器类

创建包装类实现上述接口,所有逻辑直接转发给内部持有的原生HttpClient实例:

public class HttpClientWrapper : IHttpClient
{
    private readonly HttpClient _httpClient;

    public HttpClientWrapper(HttpClient httpClient)
    {
        _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
    }

    public Task<HttpResponseMessage> GetAsync(string requestUri, CancellationToken cancellationToken = default)
    {
        return _httpClient.GetAsync(requestUri, cancellationToken);
    }

    public Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken = default)
    {
        return _httpClient.PostAsync(requestUri, content, cancellationToken);
    }

    public Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content, CancellationToken cancellationToken = default)
    {
        return _httpClient.PutAsync(requestUri, content, cancellationToken);
    }

    public Task<HttpResponseMessage> DeleteAsync(string requestUri, CancellationToken cancellationToken = default)
    {
        return _httpClient.DeleteAsync(requestUri, cancellationToken);
    }

    public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
    {
        return _httpClient.SendAsync(request, cancellationToken);
    }

    // 对应接口补充的其他方法,全部转发给内部_httpClient即可
}

3. 依赖注入注册

直接复用.NET原生的IHttpClientFactory来管理HttpClient生命周期,注册适配接口的实现:

// 注册原生IHttpClientFactory
builder.Services.AddHttpClient();

// 注册IHttpClient适配接口的实现
builder.Services.AddScoped<IHttpClient>(serviceProvider => 
{
    var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
    // 如需使用命名配置的HttpClient,这里传入配置名称即可,比如CreateClient("ThirdPartyApi")
    var httpClient = httpClientFactory.CreateClient();
    return new HttpClientWrapper(httpClient);
});

// 如果你需要特定配置的HttpClient,也可以单独注册命名实例:
builder.Services.AddHttpClient("ThirdPartyApi", client =>
{
    client.BaseAddress = new Uri("https://api.thirdparty.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.Timeout = TimeSpan.FromSeconds(10);
});

4. 业务类使用

替换原有直接注入HttpClient的逻辑,改为依赖抽象的IHttpClient接口:

public class OrderService
{
    private readonly IHttpClient _httpClient;

    public OrderService(IHttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<OrderInfo> GetRemoteOrderInfoAsync(string orderId)
    {
        var response = await _httpClient.GetAsync($"orders/{orderId}");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<OrderInfo>();
    }
}

5. 单元测试实现

现在可以直接MockIHttpClient的返回值,不需要处理真实HTTP请求,以下是Moq框架的示例:

[TestClass]
public class OrderServiceTests
{
    [TestMethod]
    public async Task GetRemoteOrderInfoAsync_ReturnsCorrectOrder()
    {
        // 构造测试返回数据
        var expectedOrder = new OrderInfo { Id = "123", Amount = 99.9 };
        var mockResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
        {
            Content = JsonContent.Create(expectedOrder)
        };

        // Mock IHttpClient行为
        var mockHttpClient = new Mock<IHttpClient>();
        mockHttpClient.Setup(c => c.GetAsync("orders/123", It.IsAny<CancellationToken>()))
                      .ReturnsAsync(mockResponse);

        // 初始化业务服务
        var orderService = new OrderService(mockHttpClient.Object);

        // 执行测试
        var result = await orderService.GetRemoteOrderInfoAsync("123");

        // 断言结果
        Assert.IsNotNull(result);
        Assert.AreEqual(expectedOrder.Id, result.Id);
        Assert.AreEqual(expectedOrder.Amount, result.Amount);
        mockHttpClient.Verify(c => c.GetAsync("orders/123", It.IsAny<CancellationToken>()), Times.Once);
    }
}

如果你不想自己做接口包装,也可以通过MockHttpMessageHandler的方式直接测试依赖原生HttpClient的类,但接口适配的方式更符合依赖倒置原则,扩展和维护成本更低。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 21:45:03