【发布时间】:2020-04-28 09:41:01
【问题描述】:
我正在寻找关于如何改进我当前设计的建议,以使用自定义 HttpClientHandler 配置测试依赖于 HttpClient 的类(以下示例)。我通常使用构造函数注入来注入在整个应用程序中一致的 HttpClient,但是因为这是在类库中,所以我不能依赖库的使用者来正确设置 HttpClientHandler。
为了进行测试,我遵循在HttpClient 构造函数中替换HttpClientHandler 的标准方法。因为我不能依赖库的使用者来注入有效的HttpClient,所以我没有将它放在公共构造函数中,而是使用带有内部静态方法(CreateWithCustomHttpClient())的私有构造函数来创建它。这背后的意图是:
- 依赖注入库不应自动调用私有构造函数。我知道,如果我将其设为公共/内部,那么一些已注册
HttpClient的 DI 库将调用该构造函数。 - 单元测试库可以使用
InternalsVisibleToAttribute调用内部静态方法
这个设置对我来说似乎很复杂,我希望有人可以提出改进建议,但我知道这可能是相当主观的,所以如果在这种情况下有任何既定的模式或设计规则要遵循,我非常感谢听到他们的消息。
我包含DownloadSomethingAsync() 方法只是为了演示为什么HttpClientHandler 需要非标准配置。默认是重定向响应在内部自动重定向而不返回响应,我需要重定向响应,以便我可以将它包装在一个报告下载进度的类中(该功能与此问题无关)。
public class DemoClass
{
private static readonly HttpClient defaultHttpClient = new HttpClient(
new HttpClientHandler
{
AllowAutoRedirect = false
});
private readonly ILogger<DemoClass> logger;
private readonly HttpClient httpClient;
public DemoClass(ILogger<DemoClass> logger) : this(logger, defaultHttpClient) { }
private DemoClass(ILogger<DemoClass> logger, HttpClient httpClient)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
[Obsolete("This is only provided for testing and should not be used in calling code")]
internal static DemoClass CreateWithCustomHttpClient(ILogger<DemoClass> logger, HttpClient httpClient)
=> new DemoClass(logger, httpClient);
public async Task<FileSystemInfo> DownloadSomethingAsync(CancellationToken ct = default)
{
// Build the request
logger.LogInformation("Sending request for download");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://example.com/downloadredirect");
// Send the request
HttpResponseMessage response = await httpClient.SendAsync(request, ct);
// Analyse the result
switch (response.StatusCode)
{
case HttpStatusCode.Redirect:
break;
case HttpStatusCode.NoContent:
return null;
default: throw new InvalidOperationException();
}
// Get the redirect location
Uri redirect = response.Headers.Location;
if (redirect == null)
throw new InvalidOperationException("Redirect response did not contain a redirect URI");
// Create a class to handle the download with progress tracking
logger.LogDebug("Wrapping release download request");
IDownloadController controller = new HttpDownloadController(redirect);
// Begin the download
logger.LogDebug("Beginning release download");
return await controller.DownloadAsync();
}
}
【问题讨论】:
标签: c# unit-testing dependency-injection inversion-of-control