C# 如何使用定制的HttpClientHandler配置来单元测试/依赖注入依赖于HttpClient的类
我正在寻找关于如何改进当前设计的建议,以测试一个依赖于C# 如何使用定制的HttpClientHandler配置来单元测试/依赖注入依赖于HttpClient的类,c#,unit-testing,dependency-injection,inversion-of-control,C#,Unit Testing,Dependency Injection,Inversion Of Control,我正在寻找关于如何改进当前设计的建议,以测试一个依赖于HttpClient和自定义HttpClientHandler配置的类(下面的示例)。我通常使用构造函数注入来注入在整个应用程序中保持一致的HttpClient,但是因为它位于类库中,我不能依靠库的使用者来正确设置HttpClientHandler 对于测试,我遵循在HttpClient构造函数中替换HttpClientHandler的标准方法。因为我不能依赖库的使用者来注入有效的HttpClient,所以我没有将其放入公共构造函数中,而是使
HttpClient
和自定义HttpClientHandler
配置的类(下面的示例)。我通常使用构造函数注入来注入在整个应用程序中保持一致的HttpClient,但是因为它位于类库中,我不能依靠库的使用者来正确设置HttpClientHandler
对于测试,我遵循在HttpClient
构造函数中替换HttpClientHandler
的标准方法。因为我不能依赖库的使用者来注入有效的HttpClient
,所以我没有将其放入公共构造函数中,而是使用带有内部静态方法(CreateWithCustomHttpClient()
)的私有构造函数来创建它。这背后的意图是:
- 依赖项注入库不应自动调用私有构造函数。我知道,如果我将其公开/内部,那么一些已经注册了
的DI库将调用该构造函数HttpClient
- 内部静态方法可以由单元测试库使用
InternalsVisibleToAttribute
DownloadSomethingAsync()
方法,只是为了演示为什么HttpClientHandler
需要非标准配置。默认情况下,重定向响应在不返回响应的情况下自动在内部重定向,我需要重定向响应,以便可以将其封装在一个类中,该类报告下载进度(的功能与此问题无关)
公共类DemoClass
{
私有静态只读HttpClient defaultHttpClient=新HttpClient(
新型HttpClientHandler
{
AllowAutoRedirect=false
});
专用只读ILogger记录器;
私有只读HttpClient HttpClient;
公共DemoClass(ILogger记录器):此(记录器,defaultHttpClient){}
私有DemoClass(ILogger记录器、HttpClient HttpClient)
{
this.logger=logger??抛出新的ArgumentNullException(nameof(logger));
this.httpClient=httpClient??抛出新的ArgumentNullException(nameof(httpClient));
}
[过时(“这仅用于测试,不应用于调用代码”)]
内部静态DemoClass CreateWithCustomHttpClient(ILogger记录器、HttpClient HttpClient)
=>新的DemoClass(记录器、httpClient);
公共异步任务下载SomeThingAsync(CancellationToken ct=default)
{
//生成请求
logger.LogInformation(“发送下载请求”);
HttpRequestMessage请求=新建HttpRequestMessage(HttpMethod.Get)http://example.com/downloadredirect");
//发送请求
HttpResponseMessage response=等待httpClient.SendAsync(请求,ct);
//分析结果
开关(响应状态代码)
{
案例HttpStatusCode。重定向:
打破
案例HttpStatusCode.no内容:
返回null;
默认值:抛出新的InvalidOperationException();
}
//获取重定向位置
Uri重定向=response.Headers.Location;
如果(重定向==null)
抛出新的InvalidOperationException(“重定向响应未包含重定向URI”);
//创建一个类来处理带有进度跟踪的下载
LogDebug(“包装发布下载请求”);
IDownloadController=新的HttpDownloadController(重定向);
//开始下载
LogDebug(“开始发布下载”);
返回wait controller.downloadsync();
}
}
在我看来,我将在中使用,并创建一个自定义依赖项注入扩展,供类库的使用者使用:
public static class DemoClassServiceCollectionExtensions
{
public static IServiceCollection AddDemoClass(
this IServiceCollection services,
Func<HttpMessageHandler> configureHandler = null)
{
// Configure named HTTP client with primary message handler
var builder= services.AddHttpClient(nameof(DemoClass));
if (configureHandler == null)
{
builder = builder.ConfigurePrimaryHttpMessageHandler(
() => new HttpClientHandler
{
AllowAutoRedirect = false
});
}
else
{
builder = builder.ConfigurePrimaryHttpMessageHandler(configureHandler);
}
services.AddTransient<DemoClass>();
return services;
}
}
您可以要求使用者必须调用AddDemoClass
才能使用DemoClass
:
var services = new ServiceCollection();
services.AddDemoClass();
通过这种方式,您可以隐藏HTTP客户端构造的详细信息
同时,在测试中,您可以模拟
IHttpClientFactory
,返回HttpClient
,以进行测试。好方法。您可以稍微修改它,以使用类型化客户机,而不是像您所示的那样手动添加为瞬态。它为你做了所有的样板设置。我非常喜欢这种方法。不幸的是,IHttpClientFactory实际上与Extension.DependencyInjection()紧密耦合。我正在努力开发这个库,使所有使用Extensions.DependencyInjection和Autofac的内部团队都能轻松使用它,并为根本不使用DI的项目提供良好的默认实现。这是非常具体的我的需要,我在问题中没有详细说明,所以这个答案对我来说仍然有效。@Brad在这种情况下,你仍然可以借用这个想法。类似于为实例化DemoService
提供工厂。
var services = new ServiceCollection();
services.AddDemoClass();