Last week, in my previous post, I wrote about the Moq.Contrib.HttpClient
library that makes unit-testing C# code that deals with HttpClient and IHttpClientFactory much easier to work with. A couple of days later, this happened.
So, first of all, a big thank you to Max Kagamine, the creator of the Moq.Contrib.HttpClient library for reviewing my blog post and for the pull-request that was made to teach me a few additional things about the library. While what I showed in the previous post works, Max pointed out a few ways to simplify the testing process.
In my approach, I had created a helper class called HttpTestingUtility
to tuck away some of the “verbosity” of the setup part of the test. This helper class did primarily three things:
- You can pass it a name so that it can create a “named client” with that name.
- You can pass in a mocked response in the form of
HttpResponseMessage
and your fake HttpClient will then return that mocked response when it is invoked. As part of this mocked response, you can also specify the HTTP Status it should produce – a200 OK
response, or a400 Bad Request
, or whatever. - It exposed the mocked HttpMessageHandler and the instance of the IHttpClientFactory so that you can make assertions with them.
Well, I learned that you can do a lot of that inline within your test itself and still keep the number of lines to a minimum. For example, to test that your “real code” is making an external HTTP call that is a GET
method, to a particular endpoint and it returns a JSON array of some numbers, you can do that like this, without the need of a separate helper class/method:
[Fact] public async Task Handle_CallsRandomNumberExternalApi() { // Arrange var handler = new Mock<HttpMessageHandler>(MockBehavior.Strict); var factory = handler.CreateClientFactory(); var numbers = new int[] { 100, 99, 98, 97, 96, 95, 94, 93, 92, 91 }; // See "MatchesQueryParameters" here for tips on matching dynamic query strings: // https://github.com/maxkagamine/Moq.Contrib.HttpClient/blob/master/Moq.Contrib.HttpClient.Test/RequestExtensionsTests.cs handler.SetupRequest(HttpMethod.Get, "http://www.randomnumberapi.com/api/v1.0/random?min=26&max=38&count=10") .ReturnsJsonResponse(numbers); var sut = new GetWeatherForecastHandler(factory); // Act await sut.Handle(new WeatherForecastRequest(), new CancellationToken()); // Assert handler.VerifyAll(); }
Above, I’m using the handler’s SetupRequest
method, passing in the HttpMethod (GET) and the API URL. Then we’re chaining on the ReturnsJsonReponse with the payload that it should return. All that can be done in a couple of lines without the need for an external helper method. The VerifyAll() will confirm that your production code is making the same HTTP call that you’ve setup in your test, utilizing the same HTTP method, returning the same output that you’ve mocked and the call is invoked exactly the same number of times you’ve specified in the test.
Is it really working?
If you’re like me, you want to go in and make sure that these tests are doing what you think they’re doing. Try changing the URL that either your test setup code is using or try changing the same in your production code. In either case, your test will fail (as it should), confirming to you that these mock helpers are in fact doing their jobs correctly. Try mismatching the HTTP method or a query string param or throw in an extra HTTP call somewhere. The tests will turn red, as they should.
Here are the couple of other tests that I had written that Max had graciously modified to fit this new approach:
[Fact] public async Task Handle_Returns5DayForecast() { // Arrange var handler = new Mock<HttpMessageHandler>(MockBehavior.Strict); var factory = handler.CreateClientFactory(); var numbers = new int[] { 100, 99, 98, 97, 96, 95, 94, 93, 92, 91 }; handler.SetupRequest(HttpMethod.Get, "http://www.randomnumberapi.com/api/v1.0/random?min=26&max=38&count=10") .ReturnsJsonResponse(numbers); var sut = new GetWeatherForecastHandler(factory); // Act var results = await sut.Handle(new WeatherForecastRequest(), new CancellationToken()); // Assert Assert.Equal(5, results.WeatherForecasts.Count()); Assert.Equal(numbers.Skip(1).Take(5), results.WeatherForecasts.Select(x => x.TemperatureC)); } [Fact] public async Task Handle_ThrowsErrorWhenRandomNumberApiCallFails() { // Arrange var handler = new Mock<HttpMessageHandler>(MockBehavior.Strict); var factory = handler.CreateClientFactory(); handler.SetupAnyRequest() .ReturnsResponse(HttpStatusCode.ServiceUnavailable); var sut = new GetWeatherForecastHandler(factory); // Act & Assert await Assert.ThrowsAsync<HttpRequestException>(async () => { await sut.Handle(new WeatherForecastRequest(), new CancellationToken()); }); }
Closing Remarks
Please go check out Max’s library at maxkagamine/Moq.Contrib.HttpClient: A set of extension methods for mocking HttpClient and IHttpClientFactory with Moq. (github.com). Give it a ⭐️. Also, you can go checkout my update repository on my GitHub account, here: