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
}
}
}
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.
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
}
}
}
and adding: services.AddSingleton<HttpClient>();
to the ConfigureServices
method of Startup.cs
.
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?
I find the simplest way is to continue using
HttpClient
, but pass in a mockingHttpClientHandler
such as https://github.com/richardszalay/mockhttpCode sample from the link above:
The Dependency Injection framework built into .NET Core ignores
internal
constructors, so it will call the parameter-less constructor in this scenario.In your unit tests, you can use the constructor accepting the
HttpClientHandler
: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
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.One way would be to abstract your HTTP functionality into a service i.e.
HttpService
which implements an interface ofIHttpService
:IHttpService
HttpService
Add to your services:
In your class you would then inject
IHttpService
as a dependency:You could then use Moq to mock
HttpService
in your unit test: