Asp.net core writing unit test details for a class using httpclient object

Time:2019-10-21

introduce

A few years ago, Microsoft introduced the httpclient class to send web requests instead of Httpwebrequest. This new class is easier to use, simpler, more asynchronous, and easy to extend.

The httpclient class has a constructor that accepts the httpmessagehandler class object. 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, which is a handler that sends a request to the network server and returns a response from the server. In this post, we will create our own httpmessagehandler by inheriting delegatinghandler.

In order to achieve the above functions, httpclient objects cannot be used directly, but need to be used together with dependency injection that allows simulation using the ihttpclientfactory interface.

Let’s forge an httpmessagehandler

In the following example, we will only discuss httpresponsemessage, not httprequestmessage.

Here 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 copy the sendasync method, in which I directly return the httpresponsemessage object passed in the constructor.

Write a service using the ihttpclientfactory interface

Next, we need to write a userservice class, which provides a 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; 
   }
  }
 }
}

Here are the user classes returned by API requests


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 a 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 a successful response and get 2 users’ information.
  • The data we expect from the service is in JSON format.
  • We use a fake handler to initialize an httpclient object, and then define the expected fake 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

summary

The author of this article explained how to forge httpclient to test the class holding httpclient object in asp.net core. Here we mainly create an httpclient object through the forged delegatinghandler object, and use ihttpclientfactory to obtain the forged httpclient to achieve the purpose.

Source code: https://github.com/lamondlu/sample_testhttpclient (local download)

Well, the above is the whole content of this article. I hope that the content of this article has some reference learning value for your study or work. If you have any questions, you can leave a message and exchange. Thank you for your support for developepaer.

How to unit test a class that consumers an httpclient with ihttpclientfactory in asp.net core?

By Anthony giretti