← Back to all articles

Mocking HttpClient in C#

Published

When writing unit tests for services that make external API calls via HttpClient, in our simplified example of ArticleService, we need to replace real API calls with mocks.

public class ArticleService(IHttpClientFactory httpClientFactory)
{
    private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;

    public async Task<Article?> GetLastArticle()
    {
        var client = _httpClientFactory.CreateClient("ArticleApi");

        using var response = await client.GetAsync("lastarticle");

        if (!response.IsSuccessStatusCode)
        {
            // Optionally log or handle the error here
            return null;
        }

        var responseJson = await response.Content.ReadAsStringAsync();
        var article = JsonSerializer.Deserialize<Article>(responseJson);

        return article;
    }
}

Simply mocking the IHttpClientFactory interface using, for example, NSubstitute, is not enough. We need to create a MockHttpMessageHandler class that inherits from HttpMessageHandler.

image

internal class MockHttpMessageHandler(HttpResponseMessage responseMessage) : HttpMessageHandler
{
    private readonly HttpResponseMessage _responseMessage = responseMessage;

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        return Task.FromResult(_responseMessage);
    }
}

MockHttpMessageHandler takes an HttpResponseMessage in its constructor (the message we expect to receive from our mock) and simply returns it in the overridden SendAsync method.

image

Source: HTTP Message Handlers in ASP.NET Web API

Testing a Successful Response

public class ApiServiceTests
{
    [Fact]
    public async Task GetLastArticle_ReturnsArticle()
    {
        // Arrange
        var expectedArticle = new Article { Id = 1, Title = "Test Title", Body = "Test Body" };
        var jsonResponse = JsonSerializer.Serialize(expectedArticle);
        var httpResponse = new HttpResponseMessage
        {
            StatusCode = HttpStatusCode.OK,
            Content = new StringContent(jsonResponse)
        };
        var mockHttpMessageHandler = new MockHttpMessageHandler(httpResponse);
        var httpClient = new HttpClient(mockHttpMessageHandler)
        {
            BaseAddress = new Uri("https://example.com/")
        };

        var mockHttpClientFactory = Substitute.For<IHttpClientFactory>();
        mockHttpClientFactory.CreateClient("ArticleApi").Returns(httpClient);

        var articleService = new ArticleService(mockHttpClientFactory);

        // Act
        var result = await articleService.GetLastArticle();

        // Assert
        Assert.NotNull(result);
        Assert.Equal(expectedArticle.Id, result.Id);
        Assert.Equal(expectedArticle.Title, result.Title);
    }
}

When creating HttpClient, we pass an instance of the MockHttpMessageHandler class to the constructor and make this HttpClient the return value of the CreateClient method of the mockHttpClientFactory instance.

Making the Mock More Flexible

We can also make the mock more flexible by using a Func delegate that will be called in the SendAsync method.

public class MockHttpMessageHandler(
    Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> sendAsyncFunc)
    : HttpMessageHandler
{
    private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>>
        _sendAsyncFunc = sendAsyncFunc;

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        return await _sendAsyncFunc(request, cancellationToken);
    }
}

In this case, we can test the exceptions that occur when making HTTP requests.

[Fact]
public async Task GetLastArticle_ThrowsHttpRequestException()
{
    // Arrange
    var mockHttpMessageHandler = new MockHttpMessageHandler(
        (r, ct) => throw new HttpRequestException());
    var httpClient = new HttpClient(mockHttpMessageHandler)
    {
        BaseAddress = new Uri("https://example.com/")
    };

    var mockHttpClientFactory = Substitute.For<IHttpClientFactory>();
    mockHttpClientFactory.CreateClient("ArticleApi").Returns(httpClient);

    var articleService = new ArticleService(mockHttpClientFactory);

    // Act & Assert
    await Assert.ThrowsAsync<HttpRequestException>(() => articleService.GetLastArticle());
}

As a result, we have a fairly simple way to mock HttpClient without external dependencies, allowing for flexible configuration of expected HTTP responses.