c# - 重构代码以允许对 HttpClient 进行单元测试
问题描述
我正在处理一段看起来像这样的代码:
public class Uploader : IUploader
{
public Uploader()
{
// assign member variables to dependency injected interface implementations
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
var handler = new HttpClientHandler();
var client = new HttpClient(handler);
result = await client.PostAsync(url, new FormUrlEncodedContent(data));
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
我正在尝试对该Upload
功能进行单元测试。特别是,我需要模拟HttpClient
. 在阅读了此处的其他答案和这 两篇文章之后,我知道解决此问题的更好方法之一是模拟它HttpMessageHandler
并将其传递给HttpClient
并让它返回我想要的任何内容。
因此,我首先将HttpClient
构造函数作为依赖项传入,从而沿着这条路径开始:
public class Uploader : IUploader
{
private readonly HttpClient m_httpClient; // made this a member variable
public Uploader(HttpClient httpClient) // dependency inject this
{
m_httpClient = httpClient;
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
var handler = new HttpClientHandler();
result = await m_httpClient.PostAsync(url, new FormUrlEncodedContent(data));
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
并添加:services.AddSingleton<HttpClient>();
到 的ConfigureServices
方法Startup.cs
。
但是现在我面临一个小问题,原始代码专门创建了一个HttpClientHandler
要传入的。那么我如何重构它以接受一个可模拟的处理程序?
解决方案
我发现最简单的方法是继续使用HttpClient
,但传入一个模拟HttpClientHandler
,例如https://github.com/richardszalay/mockhttp
来自上面链接的代码示例:
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://localhost/api/user/*")
.Respond("application/json", "{'name' : 'Test McGee'}");
// Inject the handler or client into your application code
var client = mockHttp.ToHttpClient();
var response = await client.GetAsync("http://localhost/api/user/1234");
var json = await response.Content.ReadAsStringAsync();
Console.Write(json); // {'name' : 'Test McGee'}
.NET Core 中内置的依赖注入框架会忽略internal
构造函数,因此在这种情况下会调用无参数构造函数。
public sealed class Uploader : IUploader
{
private readonly HttpClient m_httpClient;
public Uploader() : this(new HttpClientHandler())
{
}
internal Uploader(HttpClientHandler handler)
{
m_httpClient = new HttpClient(handler);
}
// regular methods
}
在您的单元测试中,您可以使用接受以下内容的构造函数HttpClientHandler
:
[Fact]
public async Task ShouldDoSomethingAsync()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://myserver.com/upload")
.Respond("application/json", "{'status' : 'Success'}");
var uploader = new Uploader(mockHttp);
var result = await uploader.UploadAsync();
Assert.Equal("Success", result.Status);
}
通常我不太喜欢使用内部构造函数来促进测试,但是,我发现这比注册共享的HttpClient
.
HttpClientFactory
可能是另一个不错的选择,但我没有玩太多,所以我只会提供我自己发现有用的信息。
推荐阅读
- angular - Angular:再次导航到页面时如何重新加载页面或触发功能?
- html - 将 div 宽度设置为 Img 内容
- powershell - 如何在一行中输出多个变量
- c# - 如何在 C# 中使用 Json.net 解析未知的 Json 文件
- c++ - registerDialogClass 会导致问题吗?
- ffmpeg - 使用 Android NDK r20 构建 FFMPEG 4.2
- entity-framework - ASP.NET Core 2.2 如何使用依赖注入将两个构造函数添加到同一个类?
- parsing - 是否可以使用 EBNF 描述块注释?
- c# - 索引文档后删除模板
- python - 为什么张量流重塑数组超出范围