Search code examples
c#asp.net-core.net-coredotnet-httpclienthttpclientfactory

Nunit 3: Test a Controller that uses IHttpClientFactory as Constructor Parameter


Update 20221024: I have used Ruikai Feng's solution in order to use Mockoon with my tests. I realize this is not a correct approach from a unit testing approach and am working to change my approach.

Update 20221019: I have been using moq to mock out the IHttpClientFactory. The reason why I wanted to instantiate it was to call mock apis created in a tool called Mockoon which replicates apis. I have been so far unable to call these APIs likely because I have not yet properly mocked the ihttpclientfactory. I appreciate all the feedback as the solution is still ongoing at this time.

I am using a .NET 6 Web API controller with IHttpClientFactory to perform external API calls. As such, I have the following constructor:

public MyController(IHttpClientFactory httpClientFactory)
{
  _httpClientFactory = httpClientFactory;
}

This works because in my Program.cs I add an HTTP Client to my builder.Services.

In my tests, how do I instantiate/set up the httpClientFactory for the controller because I need it to instantiate my controller: var controller = new MyController(httpClientFactory); generates an error since there isn't any settings added.

I ran into a similar issue with configurations from appsettings.json and resolved with ConfigurationBuilder but there doesn't seem to be a similar one for IHttpClientFactory.

If you need any more information, please let me know. Thanks!


Solution

  • In order to be able to use a properly mocked IHttpClientFactory in your unit test you need to do the following steps:

    Setup a DelegatingHandler mock

    var mockHandler = new Mock<DelegatingHandler>();
    mockHandler.Protected()
        .Setup<Task<HttpResponseMessage>>("SendAsync", It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>())
        .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK))
        .Verifiable();
    mockHandler.As<IDisposable>().Setup(s => s.Dispose());
    

    This sample mock will always return with 200 OK status code and without a response body

    • Tailor the setup for your needs

    Create an HttpClient

    var httpClient = new HttpClient(mockHandler.Object);
    

    It creates an HttpClient instance and pass the above handler to it

    Setup an IHttpClientFactory mock

    var mockFactory = new Mock<IHttpClientFactory>(MockBehavior.Strict);
    mockFactory
      .Setup(factory => factory.CreateClient())
      .Returns(httpClient)
      .Verifiable();
    

    It setups an IHttpClientFactory mock to return the above HttpClient for the CreateClient method call

    • If you use the IHttpClientFactory to create a named client then change the Setup to this .Setup(factory => factory.CreateClient(It.IsAny<string>()))

    Use the mock objects for verification

    mockFactory.Verify(factory => factory.CreateClient(), Times.Once); 
    mockHandler.Protected()
       .Verify("SendAsync", Times.Once(), It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>());