Mocking an HttpClient using an HttpClientHandler

 
 
  • Gérald Barré

Recently, I wrote a .NET client for Open Exchange Rates, a service that provides exchange rates for more than 100 currencies. You can find it on GitHub. The code is straightforward: it calls the Open Exchange Rates API using an HttpClient and deserializes the JSON response with Json.NET. This post is not about that code, but about how to add tests to this kind of HTTP client code. Testing is essential!

To test the code, you need to simulate server responses. One option is to define an interface and use a mock library to replace the HttpClient. However, I prefer keeping the HttpClient directly in the code, since it is the core of the implementation. There is a better way: HttpClient supports the concept of handlers. Handlers can modify the behavior of HttpClient – for example, adding headers, altering the request body, or replacing the response. Here's a diagram from the documentation:

HttpClient and HttpClientHandler explanationHttpClient and HttpClientHandler explanation

Let's create a handler that enables mocking server responses. A handler exposes a single method: Task<HttpResponseMessage> SendAsync(HttpRequestMessage, CancellationToken). To make it easy to mock, we can add an overload with two arguments – the HTTP method and the URL: HttpResponseMessage SendAsync(HttpMethod method, string url). This signature is simpler to set up in a mock. Here's the class:

C#
public abstract class MockHandler : HttpClientHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Task.FromResult(SendAsync(request.Method, request.RequestUri.PathAndQuery));
    }

    public abstract HttpResponseMessage SendAsync(HttpMethod method, string url);
}

Now you can easily create handlers with a mocking framework such as FakeItEasy.

C#
[TestMethod]
public async Task Main()
{
    // Create an handler and give the response of the *server*
    var handler = A.Fake<MockHandler>(opt => opt.CallsBaseMethods());
    A.CallTo(() => handler.SendAsync(HttpMethod.Get, "https://example.com/ping"))
        .ReturnsLazily(() => Success("pong"));

    // Initialize the client with the handler
    using (var client = new HttpClient(handler))
    {
        // Call the url, the response should be "pong"
        var response = await client.GetStringAsync("https://example.com/ping");
        Assert.AreEqual("pong", response);
    }
}

private static HttpResponseMessage Success(string content)
{
    var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
    response.Content = new StringContent(content);
    return response;
}

Testing a class that uses HttpClient is straightforward with a custom HttpClientHandler. This approach requires no changes to production code. Handlers also open the door to many other useful scenarios, such as retrying requests, adding custom headers, caching, and more.

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?