ASP. NET Core Write unit test details for a class that uses HttpClient objects

  • 2021-11-02 00:21:33
  • OfStack

Introduction

A few years ago, Microsoft introduced the HttpClient class instead of HttpWebRequest to send Web requests. This new class is easier to use, more concise, more asynchronous, and easy to extend.

The HttpClient class has a constructor that accepts HttpMessageHandler class objects. The HttpMessageHandler class object can accept a request (HttpRequestMessage) and return a response (HttpResponseMessage). Its function depends entirely on its implementation. By default, HttpClient uses HttpClientHandler, and HttpClientHandler is a handler that sends a request to a network server and returns a response from the server. In this blog post, we will create our own HttpMessageHandler by inheriting DelegatingHandler.

To do this, the HttpClient object cannot be used directly, but rather with dependency injection 1, which allows impersonation using the IHttpClientFactory interface.

Let's forge an HttpMessageHandler

In the following example, we will only discuss HttpResponseMessage and will not deal with HttpRequestMessage.

The following is an HttpMessageHandler object that I forged.


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);
 }
}

Here I add a constructor that requires HttpResponseMessage, and then override the SendAsync method, in which the HttpResponseMessage object passed in from the constructor is returned directly.

Write a service that uses the IHttpClientFactory interface

Next we need to write an UserService class that provides an GetUsers method to get the user list from the remote server.


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; 
   }
  }
 }
}

The following is the user class returned by Api request


public class User
{
 public string FirstName { get; set; }
 public string LastName { get; set; }
}

As you can see, using HttpClientFactory allows us to simulate HttpClient instantiation

Test service

In the following unit tests, we will use XUnit, FluentAssertion, NSubstitute

Test Scenario 1: Simulate 1 request and return 2 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");
  }
}
In the above test, we expect to get one successful response and get the information of two users. We expect the data from Service to be in JSON format. We initialize an HttpClient object with a forgery handler, and then define the desired forgery object httpClientFactoryMock. CreateClient (). Returns (fakeHttpClient);

Test Scenario 2: Simulate a 404 error and return 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();
  }
}

Similar to Test Scenario 1, when an Http request returns Not Found, its result set is Null

Summarize

In this article, the author explained how to forge an HttpClient in ASP. NET Core to test a class that holds an HttpClient object. Here, we mainly create an HttpClient object by forging DelegatingHandler object, and use IHttpClientFactory to obtain forged HttpClient to achieve our goal.

Source code of this article: https://github.com/lamondlu/Sample_TestHttpClient (local download)

Original address: How to unit test a class that consumes an HttpClient with IHttpClientFactory in ASP. NET Core?

By Anthony Giretti


Related articles: