如何为单元测试基于依赖注入实现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); } }
如果你不想自己做接口包装,也可以通过Mock
HttpMessageHandler的方式直接测试依赖原生HttpClient的类,但接口适配的方式更符合依赖倒置原则,扩展和维护成本更低。
内容的提问来源于stack exchange,提问作者Val
相关产品推荐
相关产品推荐

