chaitanya.dev
Posted on September 7, 2020
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;
}
}
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);
}
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();
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>()
);
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>()
);
}
}
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!
Posted on September 7, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.