Many enterprise applications are moving toward or have already made the transition to a microservices-based architecture. Microservices can help teams to release autonomously and deliver at a faster pace. However, when moving toward using microservices, the number of integration points within an application tends to explode in numbers. This increase in the number of integration points between the various services adds an extra layer of complexity and introduces a point of high risk for bugs and errors. This is especially true when the different microservices an application is composed of are designed and created by different teams — the risk for both small and larger misunderstandings in expected behavior is high.
To combat this, the developer community may have traditionally turned to end-to-end (E2E) tests. These tests can be really useful, but these tests can also be time-consuming, high in complexity, and costly, as you need to stand up every component of your application in order to run these tests. There is a need for tests that go beyond unit testing but don’t require the resources, time, and complexity that end-to-end tests require. This is where contract testing can be introduced.
Contract tests enable you to test the integration between your components without having to stand up everything. It is a testing technique where the consumer defines the contract, and verifications are made against this contract within the provider’s build/test lifecycle. So, this type of testing enables you to test contract agreements between a consumer endpoint and an API provider endpoint. Contract-based test suites can be combined to cover each interaction scenario that takes place between these endpoints. Essentially, you’re creating unit tests or suites of unit tests that validate that your API endpoint connections are functioning correctly and according to your “contract.”
Contract-based tests can be particularly useful when trying to focus on catching defects:
- Within the consumer workflows
- Around misunderstandings regarding endpoint configuration or payload contents
- When the provider has created breaking changes on endpoints and/or payload
There are several open source contract test frameworks available, including Spring Cloud Contract and Pact. A great feature of the Pact framework is its polyglot nature — it includes Java bindings, NodeJS bindings, Ruby bindings, and more, so it can be used with many different languages, which can be especially helpful if your microservices aren’t written in the same language.
Fire alarm analogy
To help clarify where contract testing is useful, let’s take a look at a fire alarm analogy.
In order to have confidence in a fire alarm (an essential piece of safety equipment), it needs to be tested — just like we test our applications to have confidence that they work as expected.
But, how do you test a fire alarm?
The purpose of a fire alarm is to provide an alarm in the event of a potential fire. It’s expected behavior is to detect smoke inside the house and then signal, usually through the production of a loud sound, that there is a risk of fire/smoke to alarm people in the vicinity. However, most people don’t go about testing their fire alarm by setting fire to their house (the equivalent of an end-to-end test). Instead, many people use the provided “test” button on the fire alarm to check if it is still able to produce a noise (the equivalent of a unit test).
The issue with simply using the provided “test” button is that you’re only able to test that the fire alarm is able to produce a sound, not that it produces this sound in the presence of smoke or a fire. It’s all well and good having a device that can produce sound, but if a fire alarm doesn’t do this when there is a potential fire or presence of smoke, it’s not going to make a very successful fire alarm. So, to avoid having to set the house completely ablaze, but to also ensure that our fire alarm works correctly, we need something inbetween these two levels of testing.
There are tools available (not generally for personal use, but often used by institutions) that enable us to test fire alarms with smoke (the equivalent of contract testing). These tools provide a cup that fits over the fire alarm and is able to fill with smoke. This type of testing means that we can validate that our fire alarm successfully detects smoke and produces the alarm sound without the need to create a real fire.
How many contract tests should we use?
Contract tests can be really useful, but they still require a server to start up in order to run the tests, so it’s still more expensive than unit tests. So, generally, you’ll still want more unit tests than contract tests.
In summary, contract tests can be a really valuable form of testing for your applications. They enable us to test integration points by checking each application in isolation to ensure the messages it sends or receives conforms to a shared understanding, laid out in the “contract” shared by the consumer and publisher.
To get hands-on experience with contract testing, take a look at the Open Liberty guide “Testing microservices with consumer-driven contracts.” This interactive guide makes use of the open source Pact framework and guides you through writing contract tests for the microservices within the application.
If you’re interested in seeing a live demo of how to create contract tests in an Open Liberty application, check out this recorded IBM Cloud Expert episode “How – and why – to modernize your scruffy old Java applications” (IBM Expert TV, October 2020) by Holly Cummins. If you’re primarily interested in the contract testing aspect, start the video at timestamp 27:19.
The application used in this video can be found in GitHub.