Mocking HttpClient in unit tests with Moq and Xunit when using IHttpClientFactory
Image from Pixabay

Mocking HttpClient in unit tests with Moq and Xunit when using IHttpClientFactory

Unit testing IHttpClientFactory by mocking HttpClient in .NET Core C#

.NET Core has done a great job by introducing interface for most of classes which makes them easy to write unit tests around them. However, there are a lot of classes that re commonly used which are not refactored in .NET Core. One of these classes come from namespace System.IO for file and folder operations, but luckily there are libraries that help you write testable code using System.IO classes. If you want to know more about mocking System.IO classes you can checkout Mocking System.IO filesystem in unit tests in ASP.NET Core article.

There are still a lot of classes that we use daily in our code which we do not realize we cannot easily test until we get to writing unit tests for our existing code. One of those classes is System.Net.HttpClient class. We use it so often to make web requests. Ideally when you need to mock something that is not and abstract class or interface you could always wrap it a class that implements interface which you could mock later. 

In .NET Core we got IHttpClientFactory which allows us to have multiple configurations for HttpClient instances so that we do not need to repeat client setup.

Setting up IHttpClientFactory is quite easy in ASP.NET Core container setup in Startup.cs. Since it is an interface it is easy to mock it for the class constructors, but when it comes to actual unit tests we need to mock HttpClient class instance.

Before we jump to an actual problem of writing a test for IHttpClientFactory and HttpClient which instance is create by IHttpClientFactory, let's see how the actual setup looks like in Startup.cs class file.

using System;
using System.Net;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Polly;
using Polly.Extensions.Http;

namespace MockingSample.Api
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            var retryPolicy = HttpPolicyExtensions
                .HandleTransientHttpError()
                .OrResult(msg => msg.StatusCode == HttpStatusCode.Unauthorized)
                .WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(2));

            services.AddHttpClient("sitemap", (provider, client) =>
            {
                client.BaseAddress = new Uri(@"/");
            }).AddPolicyHandler(retryPolicy);

            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

    

Now all client instances with name "sitemap" we use in our code will already have predefined base URL and retry policy configured by Polly. If you want to know more of how to easily retry and make your application more resilient to poor and unstable network connection check article Increase service resilience using Polly and retry pattern in ASP.NET Core

To make use of this injected service, we need to inject it in the class controller. Since this application is ASP.NET Core application I will inject the service directly to controller using constructor.

using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace MockingSample.Api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class DefaultController : ControllerBase
    {
        readonly IHttpClientFactory _httpClientFactory;

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

        [HttpGet]
        public async Task<IActionResult> Get()
        {
            using(var httpClient = _httpClientFactory.CreateClient("sitemap"))
            {
                var result = await httpClient.GetStringAsync("sitemap.xml");
                return Ok(result);
            }
        }

    }
}
    

If we got to HttpClient class definition you will see that it does not implement any interface we can mock. Instead it inherits HttpMessageInvoker class.

Httpclient

Going further and checking HttpMessageInvoker, you can see that it is not an abstract class nor it implements any interface other than IDisposable which is not much helpful for us in this case since we need to mock behaviors id GetStringAsync method which does not come from IDisposable.

Httpmessageinvoker

At first sight it may look as lost case, but things are not actually that bad. If you check the constructor of HttpClient you will see that it inherits and abstract class IHttpMessageHandler which can be mocked since it is an abstract class

Httpmessagehandler

HttpClient relies on the HttpMessageHandler.SendAsync method, so we can mock this method and class and pass it to the constructor or HttpClient class instance.

Let's see how our unit test for the controller method from above would look like

using AutoFixture;
using Microsoft.AspNetCore.Mvc;
using MockingSample.Api.Controllers;
using Moq;
using Moq.Protected;
using Newtonsoft.Json;
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace MockingSample.Api.Tests
{
    public class DefaultControllerTests
    {
        [Fact]
        public async Task Get_Should_Return_OK_String()
        {
            // Arrange
            var httpClientFactory = new Mock<IHttpClientFactory>();
            var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
            var fixture = new Fixture();

            mockHttpMessageHandler.Protected()
                .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
                .ReturnsAsync(new HttpResponseMessage
                {
                    StatusCode = HttpStatusCode.OK,
                    Content = new StringContent(fixture.Create<String>()),
                });

            var client = new HttpClient(mockHttpMessageHandler.Object);
            client.BaseAddress = fixture.Create<Uri>();
            httpClientFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);

            // Act
            var controller = new DefaultController(httpClientFactory.Object);
            var result =  await controller.Get();

            // Assert
            httpClientFactory.Verify(f => f.CreateClient(It.IsAny<String>()), Times.Once);

            Assert.NotNull(result);
            Assert.IsAssignableFrom<OkObjectResult>(result);
            Assert.IsAssignableFrom<String>((result as OkObjectResult)?.Value);
            Assert.False(String.IsNullOrWhiteSpace((result as OkObjectResult)?.Value as String));
        }
    }
}

    

The unit test itself does not look so sophisticated as it would be as if you would wrap HttpClient class to implementation of an interface, but this way you get to keep using IHttpClientFactory which is more beneficial for your application than adapting it to much to have simpler unit tests. 

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article