重构代码以允许对 HttpClient 进行单元测试 [英] Refactoring code to allow for unit testing of HttpClient
问题描述
我正在处理一段如下所示的代码:
I am dealing with a piece of code that looks like this:
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
.阅读此处的其他答案和这些后href="https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/" rel="nofollow noreferrer">两个文章,我知道解决这个问题的更好方法之一是模拟 HttpMessageHandler
并将其传递给 HttpClient
并让它返回我想要的任何内容.
I am trying to unit test the Upload
function. In particular, I need to mock the HttpClient
. After reading the other answers on here and these two articles, I know that one of the better ways to solve this is to mock the HttpMessageHandler
instead and pass that to HttpClient
and have it return whatever I want.
所以,我首先在构造函数中传入 HttpClient
作为依赖项:
So, I started along that path by first passing in HttpClient
in the constructor as a dependency:
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
}
}
}
并在Startup.cs
的ConfigureServices
方法中添加:services.AddSingleton
.
但现在我面临一个小问题,原始代码专门创建了一个 HttpClientHandler
来传入.那么我如何重构它以接收一个可模拟的处理程序?
But now I face a slight issue where the original code specifically creates a HttpClientHandler
to pass in. How then do I refactor that to take in a mockable handler?
推荐答案
我觉得最简单的方法是继续使用HttpClient
,但是传入一个mocking的HttpClientHandler
,比如https://github.com/richardszalay/mockhttp
I find the simplest way is to continue using HttpClient
, but pass in a mocking HttpClientHandler
such as https://github.com/richardszalay/mockhttp
来自上面链接的代码示例:
Code sample from the link above:
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
构造函数,因此在这种情况下它将调用无参数构造函数.
The Dependency Injection framework built into .NET Core ignores internal
constructors, so it will call the parameter-less constructor in this scenario.
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
的构造函数:
In your unit tests, you can use the constructor accepting the 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
更明显和独立.
Normally I'm not a big fan of having an internal constructor to facilitate testing, however, I find this more obvious and self-contained than registering a shared HttpClient
.
HttpClientFactory
可能是另一个不错的选择,但我还没有玩过太多,所以我只会提供我自己发现有用的信息.
HttpClientFactory
might be another good option, but I haven't played around with that too much, so I'll just give info on what I've found useful myself.
这篇关于重构代码以允许对 HttpClient 进行单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!