Reputation: 822
I have the following code using an HttpClient. I'm new to C# and would like to learn how to unit test my HttpClient but am not sure where to begin. Here is my code:
protected override async Task PopulateData()
{
using (var client = new HttpClient())
{
var token = "private token";
var requestUrl = api_url_here;
var authenticatedRequestUrl = requestUrl + $"{token}";
var response = await client.GetAsync(authenticatedRequestUrl);
var stringResult = await response.Content.ReadAsStringAsync();
// do something
}
}
I've seen lots of different articles suggesting different ways to unit test but I am unsure as to how to use them properly. For example, I've seen this unit test pattern on many websites:
[Test]
public async Task MockingHTTP()
{
var requestUri = new Uri("");
var expectedResponse = "Response";
var mockHandler = new Mock<HttpMessageHandler>();
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage(Net.HttpStatusCode.OK));
var httpClient = new HttpClient(mockHandler.Object);
var result = await httpClient.GetStringAsync(requestUri).ConfigureAwait(false);
Assert.AreEqual(expectedResponse, result);
}
}
However I don't know how to apply this approach to my code. Please can someone point my in the right direction in order to successfully unit test the HttpClient?
Upvotes: 0
Views: 1338
Reputation: 32694
The first thing to do when unit testing is to identify your "System Under Test" or SUT. That's the thing that you need to verify the behavior of. Any dependencies of your SUT should be mocked, you shouldn't use the real version.
In this case, you're using HttpClient, but you've got no way to use a different handler in your method. Using a different handler is the easiest way to fake responses for HttpClient. HttpClient accepts a different handler via the constructor. So you'd need to adjust your code like this:
public class YourClassName
{
private readonly HttpMessageHandler _httpMessageHandler;
public YourClassName(HttpMessageHandler httpMessageHandler)
{
_httpMessageHandler = httpMessageHandler;
}
protected override async Task PopulateData()
{
using (var client = new HttpClient(_httpMessageHandler))
{
var token = "private token";
var requestUrl = api_url_here;
var authenticatedRequestUrl = requestUrl + $"{token}";
var response = await client.GetAsync(authenticatedRequestUrl);
var stringResult = await response.Content.ReadAsStringAsync();
// do something
}
}
}
Now it's unit testable, because you can mock the HttpMessageHandler. But the code to do this is cumbersome. And considering the other flaws you've got in using HttpClient, it's probably best to not even use HttpClient in the first place. See:
What my team does is use Flurl instead. It has a nicer syntax, doesn't have the weird wonkiness with IDisposable that HttpClient has, and is easy to unit test.
protected override async Task PopulateData()
{
var token = "private token";
var requestUrl = api_url_here;
var authenticatedRequestUrl = requestUrl + $"{token}";
var stringResult = authenticatedRequestUrl.
var response = await authenticatedRequestUrl.GetAsync();
var stringResult = await response.Content.ReadAsStringAsync();
// do something
}
Then your unit test becomes a lot simpler:
[Test]
public async Task PopulateDataTest()
{
var requestUri = new Uri("SomeUriWithAToken");
var expectedResponse = "some response body";
var systemUnderTest = new YourClassName();
using (var httpTest = new HttpTest())
{
httpTest.RespondWith(expectedResponse);
var result = await systemUnderTest.PopulateData();
Assert.AreEqual(expectedResponse, result);
httpTest.ShouldHaveCalled(requestUri);
}
}
Flurl's documentation is great. You can have it return all sorts of responses, or even automatically deserialize a response to a C# class.
Upvotes: 2