Have you ever been in a situation where an API that you don’t control changed without notice and broke an application that consumes it? If so, you’re in the right place to discover one of the best strategies to mitigate this risk with something I like to call external dependency tests. These tests sometimes go under another name as well: customer-driven contract tests.
This is the final post in the Layers of Testing a Cloud Application series. I’ll continue to expand on the sample application that’s been built to date, so having a quick review of the project will make it easier to follow along.
What are External Dependency Tests For?
Most cloud applications rely on external dependencies of all shapes and sizes. They can be internal or external, and be a RESTful API, SOAP contract or a library packaged and distributed via NuGet. In any case, the application that consumes them expects the dependency to respect its contract for the foreseeable future and that any breaking changes in behaviour are communicated. This isn’t always what happens in real life though.
External dependency tests take you from being reactive to proactive to changes to contracts. In a sense, they act as a first alert to a potential breaking change. They also help you pinpoint problems much quicker. Let’s say a field was removed from the response of an API that your application depends on. Maybe the field was renamed, or the response object’s contract has changed in some other way. Having a test that fails when that field goes missing tells you exactly what’s happened.
Typical tests will look at the response code, fields returned, and the content for correctness. These tests detect when a dependency is completely unresponsive as well, so running the tests when a problem is suspected can pinpoint precisely where the problem is. Of course, you’d ideally have a separate monitoring solution such as NewRelic or DataDog to tell you something is down, but the tests are better than nothing.
When To Consider Writing Them
Not every external dependency needs to have these types of tests. I would only consider writing them for APIs or libraries that are mission critical, where a breaking change results in a large impact — monetary or otherwise — to the business.
You don’t need many external dependency tests to gain a benefit from them. They should only check the happiest of paths and a few of the potential failure cases.
When To Run The Tests
The tests need to run regularly as part of the CI/CD pipeline to gain any kind of benefit from them. This makes sure they run as often as possible, and that’s good because you probably don’t control when that dependency will be updated.
The tests can be run locally as well but it isn’t necessary to run them every single time the unit tests are run. Create a separate test playlist to only run them on demand.
What They Look Like in .NET Core
External Dependency tests follow the same principles as Acceptance Tests. An HTTP request is sent to the API, in this case Google Maps, and the response is checked. I’ve added a few tests that invokes Google’s geocoding API to validate the response to a postal code lookup is correct.
The first test simply looks for the right status code to be returned. A failure here could indicate larger problems with the external dependency.
[Fact] public async Task GivenValidRequest_ThenHasExpectedResponseCode() { var response = await HttpRequestFactory.GetAnonymous(_clientApiUrl, $"json?address=J3Y%209H5&key={_googleMapsApiKey}"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); }
Next we have a test that checks the required fields are present and has a known good value. The external dependency is respecting the contract if both these tests pass successfully. The full source code can be found here.
[Theory] [InlineData("H1B%202N9")] public async Task GivenValidRequest_ThenHasExpectedResponseItems(string postalCode) { var response = await HttpRequestFactory.GetAnonymous(_clientApiUrl, $"json?address={postalCode}&key={_googleMapsApiKey}"); dynamic responseContent = response.ContentAsDynamic(); Assert.NotNull(responseContent.results); Assert.NotNull(responseContent.results[0].place_id); Assert.NotNull(responseContent.results[0].formatted_address); }
And that’s all there is to it. These types of tests don’t take too much time to write but provide many benefits, such as early detection of a failure, breaking contract changes, and incompatability with your application.
One comment