Writing unit tests for HttpClient using NUnit and Moq in C#

chaitanyasuvarna

chaitanya.dev

Posted on September 7, 2020

Writing unit tests for HttpClient using NUnit and Moq in C#

You have a class that sends a GET request using HttpClient and consumes the response and performs further actions. Now that you have written this class you want to go ahead and write Unit Tests using NUnit for this class so that you can make sure the right URI is being called with the correct Request Headers and Request Method. How do you go about this?

Let’s take a look at the demo project that I have created to understand this better. You can find this project on my Github Page here.

I have created a class called EmployeeApiClientService that has a method called GetEmployeeAsync() that takes an employeeid as a input parameter, sends a GET request to the employee API with the employeeid in the uri and returns the Employee object back.

public class EmployeeApiClientService
{
    private readonly HttpClient employeeHttpClient;

    public EmployeeApiClientService(HttpClient httpClient)
    {
        employeeHttpClient = httpClient;
    }

    //environment specific variables should always be set in a seperate config file or database. 
    //For the sake of this example I'm initialising them here.
    public static string testDatabase = "SloughDB";
    public static string environment = "TEST";

    public async Task<Employee> GetEmployeeAsync(int employeeId)
    {
        Employee employee = null;

        //Add headers
        employeeHttpClient.DefaultRequestHeaders.Add("Accept", "application/json");
        employeeHttpClient.DefaultRequestHeaders.Add("tracking-id", Guid.NewGuid().ToString());

        //Conditional Headers
        if (environment == "TEST")
        {
            employeeHttpClient.DefaultRequestHeaders.Add("test-db", testDatabase);
        }

        HttpResponseMessage response = await employeeHttpClient.GetAsync($"http://dummy.restapiexample.com/api/v1/employee/{employeeId}");
        if (response.IsSuccessStatusCode)
        {
            employee = JsonConvert.DeserializeObject<Employee>(await response.Content.ReadAsStringAsync());
        }
        return employee;
    }
}
Enter fullscreen mode Exit fullscreen mode

Once you have finished writing your class, you start writing your unit tests but you are stuck. There’s no interface for the HttpClient class which you can use to Moq and you do not want to hit the actual end-pints during the unit tests. What do you do here?

There are multiple ways to tackle this problem :

Method 1 : Write a wrapper class for HttpClient class
This method would require you to write a wrapper class eg. HttpClientWrapper and implement all of HttpClient’s methods in your wrapper class and then use this wrapper class as a dependency instead of HttpClient in your actual class.
Then you can Mock this Wrapper class in your unit tests and verify the request details. For me, this method seemed like too much work and not a neat implementation.

So I looked around a bit and found out that the HttpClient has a constructor overload that takes a HttpMessageHandler.

public HttpClient(HttpMessageHandler handler);

And that’s how I came to the second method.

Method 2 : Mock HttpMessageHandler and pass it to your HttpClient class
HttpMessageHandler has one protected method SendAsync() which is the underlying method called by all of HttpClient’s GET/POST/PATCH/PUT Async methods. All we have to do is mock this class and setup SendAsync to accept and return our desired values as per our test cases. We can use Moq for this purpose.

Moq is a Mocking Framework used in .NET to isolate units to be tested from the underlying dependencies. We can create a Mock for HttpMessageHandler and pass it to the overloaded constructor for HttpClient.

Let’s see how we can implement this.

Mock HttpMessageHandler

We will create a mock object of HttpMessageHandler using Moq and pass it to the HttpClient class constructor and pass this HttpClient object to our EmployeeApiClientService constructor in the Test SetUp.

EmployeeApiClientService employeeApiClientService;
Mock<HttpMessageHandler> httpMessageHandlerMock;

[SetUp]
public void setUp()
{
    httpMessageHandlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
    HttpClient httpClient = new HttpClient(httpMessageHandlerMock.Object);
    employeeApiClientService = new EmployeeApiClientService(httpClient);
}
Enter fullscreen mode Exit fullscreen mode

Setup SendAsync method

Moq does not allow us to directly setup SendAsync() method because this method is protected in the HttpMessageHandler class and cannot be accessed outside the class.
We can use the Moq.Protected api, which gives us some additional methods on the mocked object, where we can access the protected members using their names using the .Protected() method.
We will now Setup SendAsync() method of the mocked HttpMessageHandler object so that it returns StatusCode 200 with Employee object in json format for which we will use the ReturnsAsync() method and we’ll make this object verifiable by using Verify() method so that we can verify the number of calls to this method, request details etc. in the assertion section.

httpMessageHandlerMock.Protected().Setup<Task<HttpResponseMessage>>(
    "SendAsync",
    ItExpr.IsAny<HttpRequestMessage>(),
    ItExpr.IsAny<CancellationToken>()
    ).ReturnsAsync(new HttpResponseMessage()
    {
       StatusCode = HttpStatusCode.OK,
       Content = new StringContent(JsonConvert.SerializeObject(new Employee()), Encoding.UTF8, "application/json")
    }).Verifiable();
Enter fullscreen mode Exit fullscreen mode

Verify the call to SendAsync

In our assertion section of the Unit Test we’ll verify that the SendAsync() method is called only once, it’s called with a GET request, the request contains the expected target uri and the request contains all the required headers.

httpMessageHandlerMock.Protected().Verify(
    "SendAsync",
    Times.Exactly(1), 
    ItExpr.Is<HttpRequestMessage>(req =>
    req.Method == HttpMethod.Get  
    && req.RequestUri.ToString() == targetUri // veryfy the RequestUri is as expected
    && req.Headers.GetValues("Accept").FirstOrDefault() == "application/json" 
    && req.Headers.GetValues("tracking-id").FirstOrDefault() != null 
    && environment.Equals("TEST") ? 
                      req.Headers.GetValues("test-db").FirstOrDefault() == testDatabase : 
                      req.Headers.GetValues("test-db").FirstOrDefault() == null 
    ),
    ItExpr.IsAny<CancellationToken>()
    );
Enter fullscreen mode Exit fullscreen mode

Complete Test Class

The complete test class with one test case to test the Request is created correctly looks something like this.

[TestFixture]
class EmployeeApiClientServiceTests
{
    EmployeeApiClientService employeeApiClientService;
    Mock<HttpMessageHandler> httpMessageHandlerMock;

    //environment specific variables should always be set in a separate config file or database. 
    //For the sake of this example I'm initialising them here.
    string testDatabase = "SloughDB";
    string environment = "TEST";

    [SetUp]
    public void setUp()
    {
        httpMessageHandlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
        HttpClient httpClient = new HttpClient(httpMessageHandlerMock.Object);
        employeeApiClientService = new EmployeeApiClientService(httpClient);
    }


    [Test]
    public async Task GivenICallGetEmployeeAsyncWithValidEmployeeId_ThenTheEmployeeApiIsCalledWithCorrectRequestHeadersAsync()
    {
        //Arrange
        int employeeId = 1;
        string targetUri = $"http://dummy.restapiexample.com/api/v1/employee/{employeeId}";
        //Setup sendAsync method for HttpMessage Handler Mock
        httpMessageHandlerMock.Protected().Setup<Task<HttpResponseMessage>>(
            "SendAsync",
            ItExpr.IsAny<HttpRequestMessage>(),
            ItExpr.IsAny<CancellationToken>()
            )
            .ReturnsAsync(new HttpResponseMessage()
            {
                StatusCode = HttpStatusCode.OK,
                Content = new StringContent(JsonConvert.SerializeObject(new Employee()), Encoding.UTF8, "application/json")
            })
            .Verifiable();

        //Act
        var employee = await employeeApiClientService.GetEmployeeAsync(employeeId);

        //Assert
        Assert.IsInstanceOf<Employee>(employee);

        httpMessageHandlerMock.Protected().Verify(
            "SendAsync",
            Times.Exactly(1), // verify number of times SendAsync is called
            ItExpr.Is<HttpRequestMessage>(req =>
            req.Method == HttpMethod.Get  // verify the HttpMethod for request is GET
            && req.RequestUri.ToString() == targetUri // veryfy the RequestUri is as expected
            && req.Headers.GetValues("Accept").FirstOrDefault() == "application/json" //Verify Accept header
            && req.Headers.GetValues("tracking-id").FirstOrDefault() != null //Verify tracking-id header is added
            && environment.Equals("TEST") ? req.Headers.GetValues("test-db").FirstOrDefault() == testDatabase : //Verify test-db header is added only for TEST environment
                                            req.Headers.GetValues("test-db").FirstOrDefault() == null
            ),
            ItExpr.IsAny<CancellationToken>()
            );
    }
}
Enter fullscreen mode Exit fullscreen mode

Thus we have seen how we can easily write unit test cases to test HttpClient calls to verify various aspects of the requests and how your class processes the responses. This method can also be used for POST/PUT/PATCH requests as all these methods in HttpClient use HttpMessageHandler’s SendAsync() method under the hood.

I hope you found this interesting. Thanks for reading!

💖 💪 🙅 🚩
chaitanyasuvarna
chaitanya.dev

Posted on September 7, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related