Mocking is a widespread practice when unit testing, since it makes our lives so much easier. It usually is just a matter of creating a mock of the desired interface, setting up the behavior, and passing it through dependency injection. But then, we stumble upon the HttpClient in .NET, which has no interface. So how can we mock the HttpClient then? Luckily, we are not the first ones with this problem.
Context
In this topic, we will be working with the SerialKeyRetriever class. It is the latest invention of our company and it is expected to boost our stock prices by insane amounts. We have only the GetSerialKeyAsync method, which retrieves a serial key through a GET request, based on the received user id. Powerful, isn’t it?
public class SerialKeyRetriever
{
private readonly HttpClient _httpClient;
public SerialKeyRetriever(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<string> GetSerialKeyAsync(int id)
{
var url = @$"https://www.mysuperserialkeystorage.com/users/{id}";
var response = await _httpClient.GetAsync(url);
return await response.Content.ReadAsStringAsync();
}
}
How to mock the HttpClient
Mocking the HttpClient using the MockHttp library
In my opinion, the easiest way to accomplish our goal is to use the MockHttp NuGet package. Let’s analyze the code below to see how we can achieve that with this package.
private MockHttpMessageHandler _mockHttpMessageHandler;
[Test]
public async Task ShouldReturnSerialKey()
{
//Arrange
var expectedSerial = "aaa-bbb-ccc";
var id = 1;
var retriever = CreateSerialKeyRetriever();
_mockHttpMessageHandler.When($"*users/{id}").Respond(new StringContent(expectedSerial));
//Act
var actualSerial = await retriever.GetSerialKeyAsync(1);
//Assert
Assert.That(actualSerial, Is.EqualTo(expectedSerial));
}
private SerialKeyRetriever CreateSerialKeyRetriever()
{
_mockHttpMessageHandler = new MockHttpMessageHandler();
var httpClient = new HttpClient(_mockHttpMessageHandler);
return new SerialKeyRetriever(httpClient);
}
We first instantiate our SerialKeyRetriever instance in the CreateSerialKeyRetriever method. On line 22, we create a MockHttpMessageHandler, provided by the NuGet package mentioned earlier. That is what will allow us to set up the desired behavior of our HttpClient.
Then, on line 11 where we set up our mock. Through the When method, we can specify which URIs we should respond to, and we should respond. In the current example, for every request that ends with “users/1” we will return the serial “aaa-bbb-ccc”. Afterward, with the Respond method, we define the content of the returned HttpResponseMessage. There are several different overloads for this method, that allows us to vastly customize the response.
Mocking the HttpClient using only Moq
Yes! It is possible to mock the HttpClient using only the Moq framework, although it gets slightly more verbose. Let’s check it out:
[Test]
public async Task ShouldReturnSerialKey()
{
//Arrange
var expectedSerial = "aaa-bbb-ccc";
var retriever = CreateSerialKeyRetriever();
var id = 1;
_httpMessageHandlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(x => x.RequestUri.AbsoluteUri.EndsWith($"users/{id}")),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage()
{
Content = new StringContent(expectedSerial),
});
//Act
var actualSerial = await retriever.GetSerialKeyAsync(id);
//Assert
Assert.That(actualSerial, Is.EqualTo(expectedSerial));
}
private SerialKeyRetriever CreateSerialKeyRetriever()
{
_httpMessageHandlerMock = new Mock<HttpMessageHandler>();
var httpClient = new HttpClient(_httpMessageHandlerMock.Object);
return new SerialKeyRetriever(httpClient);
}
Again, we are creating the SerialKeyRetriever in the CreateSerialKeyRetriever method. And very similarly, we are creating a mock of HttpMessageHandler and passing it to the HttpClient. Then back in the test method, on line 9 we begin setting up the HttpMessageHandler behavior. Here is how we do it:
- Line 10: The Protected method call enables us to set up protected methods in the HttpMessageHandler class
- Line 12: Since the method is protected, we have no direct access to it, so we must use its name.
- Line 13: Here we define which HttpRequestMessage we are expecting. Like the other method, it expects a Uri that ends with “users/1“. Note that we use the ItExpr.Is instead of It.Is, since it’s what enables us to set up protected methods
- Line 15-17: How the HttpResponseMessage should be for the scenario we set up.
As you can see, it is possible to mock the HttpClient without any extra NuGet package (besides Moq, of course), but it requires a bit more work. The advantage is that we don’t have to add another dependency to our solution, which can be hard or bureaucratic in some scenarios.