Easy HttpClient mocking

Xavier Solau
YounitedTech
5 min readSep 13, 2021

--

In this article I will explain how you can mock the HttpClient in your C# unit tests. In addition, I will give you a mocking example using the HttpClientMockBuilder I have written to make HttpClient mocking easier with a Fluent like interface.

You can find the Github source repository of the project providing the HttpClientMockBuilder here.

Why HttpClient mocking is an issue

If the HttpClient was an interface, it would have been easy to mock it as usual with your favorite mocking library (like Moq or NSubstitute) and I wouldn’t need to write this article. Unfortunately it is not an interface so it is not that easy to create a mock from it.

The mocking library provides some ways of mocking a class but this is not without some caveats. For instance it is easy to mock public virtual methods of a class but it’s getting more complicated if the methods you want to mock is not public or it is even worse if it is not virtual.

Photo by Susan Q Yin on Unsplash

How to mock the HttpClient

The HttpClient is not an interface but hopefully it can be instantiated with a HttpMessageHandler constructor parameter.

The good point about the HttpMessageHandler is that it is an abstract class with an abstract SendAsync method. In other words it is possible to mock it or to make a specific implementation to use it in your unit tests.

Photo by AbsolutVision on Unsplash

That said, the method is protected so depending of the mocking library you use, it is not necessarily straight forward to set up your mock and sometime it won’t even be possible.

The Moq library provides some ways to mock a protected method from a class. It can be used with the Protected method like this:

// Instantiate the mock object
var httpMessageHandlerMock = new Mock<HttpMessageHandler>();
// Set up the SendAsync method behavior.
httpMessageHandlerMock
.Protected() // <= here is the trick to set up protected!!!
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage());
// create the HttpClient
var httpClient = new HttpClient(httpMessageHandlerMock.Object);

In order to get rid of those protected issues, we are going the create a custom mock implementation of the HttpMessageHandler and override the SendAsync method.

public class HttpMessageHandlerMock : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
return Task.FromResult(new HttpResponseMessage());
}
}

Now we can just instantiate our HttpClient with the HttpMessageHandlerMock:

// create the HttpClient
var httpClient = new HttpClient(new HttpMessageHandlerMock());

Using HttpClientMockBuilder

So far we have seen how the HttpClient can be mocked. Unfortunately it is quite verbose to set up the expected mock behavior.

This is where the HttpClientMockBuilder can help a lot!

Photo by Pablo Arroyo on Unsplash

In order to use it we need to install the Nuget:

dotnet add package SoloX.CodeQuality.Test.Helpers --version 2.0.7

Let’s see an example where we use it to define a mock responding a Json object (Message) on a GET request with the URL (http://host/hello).

First add the using statement:

using SoloX.CodeQuality.Test.Helpers.Http;

Then we can just set up the mock with the builder:

// Build the mock with a request configured and responding
// with a JSON data content.

var httpClient = new HttpClientMockBuilder()
.WithBaseAddress(new Uri("http://host"))
.WithRequest("/Hello")
.RespondingJsonContent<Message>(
request => new Message()
{
Title = "Hello",
Body = "World!"
})
.Build();

The WithBaseAddress allows to setup the HttpClient with a base address.

The WithRequest tells that the Http mock is supposed to respond on a given endpoint.

Note that WithRequest has an optional parameter to specify the HttpMethod. By default it’s using HttpMethod.Get.

The RespondingJsonContent<TContent> tells that the Http mock is supposed to respond on the request with a Json content of type TContent resulting of the given response handler.

Note that the request is provided as argument to the response handler.

The Build will actually create the HttpClient instance.

Note that you can define several requests before the Build call if you need to.

Finally we can use the resulting HttpClient as usual!

// Let's make a request.
var response = await httpClient.GetFromJsonAsync<Message>("Hello");
// Check that the response match the expected value.
response.Title.Should().Be("Hello");
response.Body.Should().Be("World!");

Now that we have seen a simple use case, we can go a deep further. We are going to mock a POST endpoint (http://host/birthday) with some Json data (a Person object) in the request body and responding back an other Json object (a DateTime).

// Build the mock with a request configured and responding with
// a JSON data content.

var httpClient = new HttpClientMockBuilder()
.WithBaseAddress(new Uri("http://host"))
.WithJsonContentRequest<Person>("/Birthday", HttpMethod.Post)
.RespondingJsonContent(person => DateTime.Today)
.Build();

The WithJsonContentRequest<TContent> tells that the Http mock is supposed to respond on a given POST endpoint and that the request is supposed to have a Json content of type TContent.

Note that the response handler provided to RespondingJsonContent has directly got the Person object from the request body as argument.

Once again the mocked HttpClient can be used as usual:

// Let's make a request
var response = await httpClient.PostAsJsonAsync(
"Birthday",
new Person()
{
FirstName = "John",
LastName = "Doe"
});
// Ensure request success.
response.EnsureSuccessStatusCode();
// Check that the response matches the expected value.
var responseData = await response.Content
.ReadFromJsonAsync<DateTime>();
responseData.Should().Be(DateTime.Today);

As you can see it has never been so easy to create a HttpClient mock with the HttpClientMockBuilder so give it a try and don’t hesitate to post your feedback here or on the Github project page!

--

--