How to mock IHttpClientFactory using typed clients

Viewed 2415

I am trying to properly add HttpClient in my app while also allowing for mocking away the client factory for unit testing purposes. I am using this as as resource.

In my Startup.cs

services.AddHttpClient<IUploader, Uploader>()
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        if (somecheckismet)
            return new HttpClientHandler() { ServerCertificateCustomValidationCallback = delegate { return true; } };
        return new HttpClientHandler();
    });

And my Uploader.cs

public class Uploader : IUploader
{
    private readonly HttpClient m_httpClient;

    public Uploader(HttpClient client)
    {
        m_httpClient = client;
    }

    public async Task<string> UploadData(string url, Dictionary<string, string> data)
    {
        HttpResponseMessage result;
        try
        {
            result = await m_httpClient.PostAsync(url, new FormUrlEncodedContent(data));
            if (result.StatusCode != HttpStatusCode.OK)
            {
                return "Some error message;
            }
            else
            {
                return null; // Success!
            }
        }
        catch (Exception ex)
        {
            return "Uh-oh!";
        }
    }
}

As you can see I am trying to dependency inject HttpClient into the Uploader. However, now that I've done this, how do I go about unit testing the Uploader? Previously, it would have worked to dependency inject HttpClientHandler which can be mocked away. However, now since I am trying to use IHttpClientFactory, it seems that I have to inject HttpClient.

Any help would be greatly appreciated.

2 Answers

Seems like you could just continue mocking the HttpClientHandler and use it to create the HttpClient

public void TestMethod() 
{
    var handler = // Mock the HttpClientHandler;
    var client = new HttpClient(handler);
    var uploader = new Uploader(client);

    // Draw the rest of the owl
}

You don't actually need to moq your HttpClientFactory. HttpClientFactory is just a helper class that returns HttpClient instances from a pool. In a unit testing scenario you are going to setup the HttpClient instance directly. Your startup.cs never enters the picture because you aren't starting up your entire application.

Your Uploader class is already setup for unit testing in that your are passing in your HttpClient instance. To unit test around HttpClient, you don't moq out the HttpClient instance, but replace the HttpMessageHandler that HttpClient uses. Here's a link to a great post on all the details

Code lifted directly from the article

// ARRANGE
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
handlerMock
   .Protected()
   // Setup the PROTECTED method to mock
   .Setup<Task<HttpResponseMessage>>(
      "SendAsync",
      ItExpr.IsAny<HttpRequestMessage>(),
      ItExpr.IsAny<CancellationToken>()
   )
   // prepare the expected response of the mocked http call
   .ReturnsAsync(new HttpResponseMessage()
   {
      StatusCode = HttpStatusCode.OK,
      Content = new StringContent("[{'id':1,'value':'1'}]"),
   })
   .Verifiable();

// use real http client with mocked handler here
var httpClient = new HttpClient(handlerMock.Object)
{
   BaseAddress = new Uri("http://test.com/"),
};

var subjectUnderTest = new MyTestClass(httpClient);

// ACT
var result = await subjectUnderTest
   .GetSomethingRemoteAsync('api/test/whatever');

// ASSERT
result.Should().NotBeNull(); // this is fluent assertions here...
result.Id.Should().Be(1);

// also check the 'http' call was like we expected it
var expectedUri = new Uri("http://test.com/api/test/whatever");

handlerMock.Protected().Verify(
   "SendAsync",
   Times.Exactly(1), // we expected a single external request
   ItExpr.Is<HttpRequestMessage>(req =>
      req.Method == HttpMethod.Get  // we expected a GET request
      && req.RequestUri == expectedUri // to this uri
   ),
   ItExpr.IsAny<CancellationToken>()
);
Related