Effectively stubbing remote HTTP service dependencies with HttpClient Interception in C#
Posted on Thursday, 21st May 2020
Recently I’ve been reading the new Software Engineering at Google book where I’ve found the chapters on testing to be particularly interesting. One paragraph that especially resonated with my own personal experiences with testing was the following excerpt:
This chapter got me thinking a lot about my preference for testing units of behaviour as opposed to implementation details, and as someone that works with a lot of services that communicate via HTTP there’s one particular library that’s helped me time and time again, so much so that I feel deserves its own post.
Introducing HttpClient Interception
From a high level, HttpClient Interception is a .NET Standard library, written in C# that’s designed to intercept server-side HTTP dependencies.
Prior to learning about it the two patterns I most frequently used in order to stub responses from an upstream API were either to create a custom HttpMessageHandler
(which would return desired responses/assertions on a given input), or create an interface over an HttpClient
. Both approaches tend to be painful (the former being difficult to mock due to lack of an interface and the latter somewhat the same).
HttpClient Interception, the brainchild of Martin Costello, aims to alleviate these pains making Black Box testing code that depends on one or more external HTTP APIs much less intrusive.
Let’s take a look
HttpClient Interception offers a convenient builder pattern over returning a desired response for a given input. Let’s take a look at what a simple test would look like this:
In this example we create an instance of HttpRequestInterceptionBuilder
then specify the request we wish to match against and the response we’d like to return. We then register the builder against our HttpClientInterceptionOptions
type and use it to create an instance of an HttpClient
.
Now any requests that match our builder’s parameters will return the response defined (a 500
response code) without the request leaving the HttpClient
.
Missing Registrations
At this point it’s worth calling out the ThrowOnMissingRegistration
property.
Setting ThrowOnMissingRegistration
to true
ensures no request leaves an HttpClient
created or modified by HttpClient Interception. This is helpful for ensuring you have visibility over unmatched requests, otherwise leaving ThrowOnMissingRegistration
as false could mean your request gets handled by the real upstream service, causing a false positive or having a negative impact on your test without you being aware of it.
Though naturally it all depends on your use case so in some instances this may actually be a desired behaviour so consideration is required.
Matching multiple requests
If you want to stub the response to more than one HTTP request you can register multiple builders like so:
Here I’ve registered two separate endpoints on the same HttpClientInterceptorOptions
object which is then used to create our HttpClient
. Now requests to any of the two registered endpoints will result in either a Not Found response (404) or an Internal Server Error (500) response.
Creating an HttpMessageHandler
Not all libraries will enable you to use a custom HttpClient, some may only offer you the ability to add a new HttpMessageHandler
, thankfully HttpClient Interception can also create an HttpMessageHandler
that can be passed to the HttpClient
.
For example:
Using HttpClientInterception with ASP.NET Core and IHttpClientFactory.
Now we’ve seen what HttpClient Interception can offer, let’s take a look at how we could use it to test an ASP.NET Core application depends on an upstream API. This example is taken straight from the Sample application within the HttpClient Interception repository.
Imagine we have an application that makes a remote call to GitHub’s API via Refit. This is what the controller action might look like:
To test this we can utilise HttpClient Interception to stub the response from GitHub’s API with the following fixture (based on the common WebApplicationFactory approach documented here).
Notice that when overriding ConfigureWebHost
we’re taking advantage of the IHttpMessageHandlerBuilderFilter
interface to add an additional HttpMessageHandler
(created by HttpClient Interception) to any HttpClient
created via the IHttpClientFactory API.
It’s worth noting that you can also register the HttpMessageHandler
using the ConfigurePrimaryHttpMessageHandler
(see the docs here) method that lives on the IHttpClientBuilder
interface.
Now we can easily control the responses from the GitHub API from within our tests, without having to mock any internal implementation details of our application.
On Closing
Hopefully this post has demonstrated just how helpful and versatile HttpClient Interception can be and how it can create maintainable tests that don’t break the moment you change an implementation detail. I’d highly recommend you check it our as this post only scratches the surface of what it can do.
I’d like to close this post with a quote from Kent Beck:
“I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence.”
Enjoy this post? Don't be a stranger!
Follow me on Twitter at @_josephwoodward and say Hi! I love to learn in the open, meet others in the community and talk Go, software engineering and distributed systems related topics.