Skip to content

Faking HttpClient

Let's assume that you want to create a fake HttpClient so you can dictate the behavior of the GetAsync(String) method. This seems like it would be a straightforward task, but it's complicated by the design of HttpClient, which is not faking-friendly.

A working Fake

First off, let's look at the declaration of GetAsync:

public Task<HttpResponseMessage>GetAsync(string? requestUri)

This method is neither virtual nor abstract, and so can't be overridden by FakeItEasy.

As a workaround, we can look at the definition of GetAsync and see that the method eventually ends up calling HttpMessageHandler.SendAsync(HttpRequestMessage, CancellationToken) on an HttpMessageHandler that can be supplied via the HttpClient constructor.

HttpMessageHandler.SendAsync is protected, which makes it less convenient to override than a public method. We need to specify the call by name, and to give FakeItEasy a hint about the return type, as described in Specifying a call to any method or property.

With this knowledge, we can write a passing test:

This is a simplified example

In the interest of brevity, we create a Fake, exercise it directly, and check its behavior. A more realistic example would create the Fake as a collaborator of some production class (the "system under test") and the Fake would not be called directly from the test code.

[Fact]
public async Task FakeAnyMethodWay()
{
    using var response = new HttpResponseMessage
    {
        Content = new StringContent("FakeItEasy is fun")
    };

    var handler = A.Fake<HttpMessageHandler>();
    A.CallTo(handler)
        .WithReturnType<Task<HttpResponseMessage>>()
        .Where(call => call.Method.Name == "SendAsync")
        .Returns(response);

    using var client = new HttpClient(handler);

    var result = await client.GetAsync("https://fakeiteasy.github.io/docs/");
    var content = await result.Content.ReadAsStringAsync();
    content.Should().Be("FakeItEasy is fun");
}

Easier and safer call configuration

The above code works, but specifying the method name and return type is a little awkward. A FakeableHttpMessageHandler class can be used to clean things up and to also supply a little compile-time safety by ensuring we're configuring the expected method.

public abstract class FakeableHttpMessageHandler : HttpMessageHandler
{
    public abstract Task<HttpResponseMessage> FakeSendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken);

    // sealed so FakeItEasy won't intercept calls to this method
    protected sealed override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        => this.FakeSendAsync(request, cancellationToken);
}

[Fact]
public async Task FakeByMakingMessageHandlerFakeable()
{
    using var response = new HttpResponseMessage
    {
        Content = new StringContent("FakeItEasy is fun")
    };

    var handler = A.Fake<FakeableHttpMessageHandler>();
    A.CallTo(() => handler.FakeSendAsync(
            A<HttpRequestMessage>.Ignored, A<CancellationToken>.Ignored))
        .Returns(response);

    using var client = new HttpClient(handler);

    var result = await client.GetAsync("https://fakeiteasy.github.io/docs/");
    var content = await result.Content.ReadAsStringAsync();
    content.Should().Be("FakeItEasy is fun");
}

Faking PostAsync

The techniques above can be used to intercept calls to methods other than GetAsync as well.

Consider this test, which ensures that the correct content is passed to HttpClient.PostAsync.

Does not work for .NET Framework

When run under .NET Framework, the request content is disposed as soon as the request is made, as explained in HttpClient source code comments.

[Fact]
public async Task FakePostAsync()
{
    var handler = A.Fake<FakeableHttpMessageHandler>();

    using var client = new HttpClient(handler);

    using var postContent = new StringContent("my post");
    await client.PostAsync(
        "https://fakeiteasy.github.io/docs/", postContent);

    A.CallTo(() => handler.FakeSendAsync(
            A<HttpRequestMessage>.That.Matches(
                m => m.Content!.ReadAsStringAsync().Result == "my post"),
            A<CancellationToken>.Ignored))
        .MustHaveHappened();
}