How to unit test a class that consumes an HttpClient with IHttpClientFactory in ASP.NET Core?
Introduction
A few years ago, Microsoft introduced the HttpClient class as a modern substitute for HttpWebRequest to make web requests from .NET applications. Not only is this new API much easier to use, cleaner, and asynchronous, but it is also easily expandable.
The HttpClient class has a constructor that accepts a HttpMessageHandler.
The latter is an object that accepts a request (HttpRequestMessage) and returns a response (HttpResponseMessage); the way it does it is completely dependent on the implementation. By default, HttpClient uses HttpClientHandler, a handler that sends a request to a server on the network and returns the response from the server. In this article we will create our own implementation of an HttpMessageHandler by inheriting an abstract class named DelegatingHandler.
Finally, for all this to be possible, HttpClient must not be used directly, but used with the dependency injection that allow mocking by using IHttpClientFactory interface.
Let’s create a fake HttpMessageHandler
For this example we will talk only about HttpResponseMessage, I won’t treat HttpRequestMessage.
All along this article I will demonstrate how to unit test a class by “mocking” the Http Response.
Here is a sample of a fake HttpMessageHandler:
public class FakeHttpMessageHandler : DelegatingHandler { private HttpResponseMessage _fakeResponse; public FakeHttpMessageHandler(HttpResponseMessage responseMessage) { _fakeResponse = responseMessage; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return await Task.FromResult(_fakeResponse) ; } }
I just override SendAsync and code my own implementation method because I inherit from an abstract class named DelegatingHandler as we talked in introduction.
Once done we just need to add a constructor that accept in parameter a HttpResponseMessage. That’s it ! 🙂
Let’s write a service that uses IHttpClientFactory interface
We will build a service that requests a list of user over a http call to a remote resource:
public class UserService { private readonly IHttpClientFactory _httpFactory; public UserService(IHttpClientFactory httpFactory) { _httpFactory = httpFactory; } public async Task<List<User>> GetUsers(string url) { using (HttpClient httpclient = _httpFactory.CreateClient()) using (HttpResponseMessage response = await httpclient.GetAsync(url)) { if (response.StatusCode == HttpStatusCode.OK) { List<User> users = await response.Content.ReadAsAsync<List<User>>(); return users; } return null; } } }
User contract:
public class User { public string FirstName { get; set; } public string LastName { get; set; } }
As you can see, using IHttpClientFactory allows us to mock the HttpClient instanciation
Let’s test this!
In these unit tests we will use XUnit, FluentAssertion and NSubstitute
Scenario 1: let’s mock a call that return two users
public class UserServiceTests { [Fact] public async Task WhenACorrectUrlIsProvided_ServiceShouldReturnAlistOfUsers() { // Arrange var users = new List<User> { new User { FirstName = "John", LastName = "Doe" }, new User { FirstName = "John", LastName = "Deere" } }; var httpClientFactoryMock = Substitute.For<IHttpClientFactory>(); var url = "http://good.uri"; var fakeHttpMessageHandler = new FakeHttpMessageHandler(new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent(JsonConvert.SerializeObject(users), Encoding.UTF8, "application/json") }); var fakeHttpClient = new HttpClient(fakeHttpMessageHandler); httpClientFactoryMock.CreateClient().Returns(fakeHttpClient); // Act var service = new UserService(httpClientFactoryMock); var result = await service.GetUsers(url); // Assert result .Should() .BeOfType<List<User>>() .And .HaveCount(2) .And .Contain(x => x.FirstName == "John") .And .Contain(x => x.LastName == "Deere") .And .Contain(x => x.LastName == "Doe"); } }
As you can see, we expect an HttpStatusCode to “OK” and two users (set in the content).
We expect data in JSON format in the service : List<User> users = await response.Content.ReadAsAsync<List<User>>();
That’s why we need to set the encoding and the media type, else, by default it’s considered as text/plain: new StringContent(JsonConvert.SerializeObject(users), Encoding.UTF8, “application/json”)
We instantiate the HttpClient with the fake handler and then we define our expectation for the mock : httpClientFactoryMock.CreateClient().Returns(fakeHttpClient);
To finish we just need to make our assertions and we are done 🙂
Scenario 2: let’s mock a call that uses a bad Url an return Http 404 and null data
public class UserServiceTests { [Fact] public async Task WhenABadUrlIsProvided_ServiceShouldReturnNull() { // Arrange var httpClientFactoryMock = Substitute.For<IHttpClientFactory>(); var url = "http://bad.uri"; var fakeHttpMessageHandler = new FakeHttpMessageHandler(new HttpResponseMessage() { StatusCode = HttpStatusCode.NotFound }); var fakeHttpClient = new HttpClient(fakeHttpMessageHandler); httpClientFactoryMock.CreateClient().Returns(fakeHttpClient); // Act var service = new UserService(httpClientFactoryMock); var result = await service.GetUsers(url); // Assert result .Should() .BeNullOrEmpty(); } }
Like the service is implemented, if there is an http not found, the result set is null, simply! 😉
You are now able to unit test your classes that make http calls wuth HttpClient easily.
Hope this article helped you 🙂