How can I use FakeItEasy with HttpClient, in a unit test?

Here's my (more or less) general purpose FakeHttpMessageHandler.

public class FakeHttpMessageHandler : HttpMessageHandler
{
    private HttpResponseMessage _response;

    public static HttpMessageHandler GetHttpMessageHandler( string content, HttpStatusCode httpStatusCode )
    {
        var memStream = new MemoryStream();

        var sw = new StreamWriter( memStream );
        sw.Write( content );
        sw.Flush();
        memStream.Position = 0;

        var httpContent = new StreamContent( memStream );

        var response = new HttpResponseMessage()
        {
            StatusCode = httpStatusCode,
            Content = httpContent
        };

        var messageHandler = new FakeHttpMessageHandler( response );

        return messageHandler;
    }

    public FakeHttpMessageHandler( HttpResponseMessage response )
    {
        _response = response;
    }

    protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken )
    {
        var tcs = new TaskCompletionSource<HttpResponseMessage>();

        tcs.SetResult( _response );

        return tcs.Task;
    }
}

Here is an example of it being used from one of my tests that expects some JSON as a return value.

const string json = "{\"success\": true}";

var messageHandler = FakeHttpMessageHandler.GetHttpMessageHandler( 
    json, HttpStatusCode.BadRequest );
var httpClient = new HttpClient( messageHandler );

You would now inject httpClient into your class under test (using whatever injection mechanism you prefer) and when GetAsync is called your messageHandler will spit back the result you told it to.


FakeItEasy, like most mocking libraries, does not create proxies for non-abstract components. In the case of HttpClient, the GetAsync and PostAsync methods are neither virtual nor abstract, so you can't create stub implementations of them directly. See https://github.com/FakeItEasy/FakeItEasy/wiki/What-can-be-faked.

In this case, you need a different abstraction as a dependency - one which HttpClient can fulfill, but so could other implementations, including mocks/stubs.


You could also create an AbstractHandler on which you can intercept a public abstract method. For instance:

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

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

Then you can intercept calls to the AbstractHandler.SendAsync(HttpMethod method, string url) like:

// Arrange
var httpMessageHandler = A.Fake<AbstractHandler>(options => options.CallsBaseMethods());
A.CallTo(() => httpMessageHandler.SendAsync(A<HttpMethod>._, A<string>._)).Returns(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Result")});
var httpClient = new HttpClient(httpMessageHandler);

// Act
var result = await httpClient.GetAsync("https://google.com/");

// Assert
Assert.Equal("Result", await result.Content.ReadAsStringAsync());
A.CallTo(() => httpMessageHandler.SendAsync(A<HttpMethod>._, "https://google.com/")).MustHaveHappenedOnceExactly();

More information can be found on this blog: https://www.meziantou.net/mocking-an-httpclient-using-an-httpclienthandler.htm


I did something like this when I needed to interact with the Gravatar service. I tried to use fakes/mocks but found it was impossible with HttpClient. Instead, I came up with a custom HttpMessageHandler class that lets me pre-load the expected response, along these lines:

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Tigra.Gravatar.LogFetcher.Specifications
    {
    /// <summary>
    ///   Class LoggingHttpMessageHandler.
    ///   Provides a fake HttpMessageHandler that can be injected into HttpClient.
    ///   The class requires a ready-made response message to be passed in the constructor,
    ///   which is simply returned when requested. Additionally, the web request is logged in the
    ///   RequestMessage property for later examination.
    /// </summary>
    public class LoggingHttpMessageHandler : DelegatingHandler
        {
        internal HttpResponseMessage ResponseMessage { get; private set; }
        internal HttpRequestMessage RequestMessage { get; private set; }

        public LoggingHttpMessageHandler(HttpResponseMessage responseMessage)
            {
            ResponseMessage = responseMessage;
            }

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

Then my test context setup goes something like this:

public class with_fake_gravatar_web_service
    {
    Establish context = () =>
        {
        MessageHandler = new LoggingHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK));
        GravatarClient = new HttpClient(MessageHandler);
        Filesystem = A.Fake<FakeFileSystemWrapper>();
        Fetcher = new GravatarFetcher(Committers, GravatarClient, Filesystem);
        };

    protected static LoggingHttpMessageHandler MessageHandler;
    protected static HttpClient GravatarClient;
    protected static FakeFileSystemWrapper Filesystem;
    }

Then, here's an example of a test (specification) that uses it:

[Subject(typeof(GravatarFetcher), "Web service")]
public class when_fetching_imagaes_from_gravatar_web_service : with_fake_gravatar_web_service
    {
    Because of = () =>
        {
        var result = Fetcher.FetchGravatars(@"c:\"); // This makes the web request
        Task.WaitAll(result.ToArray());
        //"http://www.gravatar.com/avatar/".md5_hex(lc $email)."?d=404&size=".$size; 
        UriPath = MessageHandler.RequestMessage.RequestUri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
        };

    It should_make_request_from_gravatar_dot_com =
        () => MessageHandler.RequestMessage.RequestUri.Host.ShouldEqual("www.gravatar.com");

    It should_make_a_get_request = () => MessageHandler.RequestMessage.Method.ShouldEqual(HttpMethod.Get);
    // see https://en.gravatar.com/site/check/[email protected]
    It should_request_the_gravatar_hash_for_tim_long =
        () => UriPath.ShouldStartWith("avatar/df0478426c0e47cc5e557d5391e5255d");

    static string UriPath;
    }

You can see the full source at http://stash.teamserver.tigranetworks.co.uk/users/timlong/repos/tigra.gravatar.logfetcher/browse