As applications grow it is normal to break them down into smaller pieces that are easy to combine and maintain. The challenge comes in the integration between these pieces as an small change in one of them may break the communication with the others, and we may not notice it until we get emails from users. We are going to see how to detect this automatically using contract testing.
Do we need a new type of tests?
Our applications already have many types of automated tests, for example:
- Smoke tests: do basic checks like ensuring that the communication with the database works as expected.
- Unit tests: check small features isolatedly.
- Integration tests: check that features work well together.
- Security tests: ensure that only the authorised users can access to them.
- Performance tests: check that users get the results they expect in a sensible time
What they have in common is that they focus on an specific service, and the only thing that they can do about others is to simulate the results they would provide (e.g. using Wiremock). However, if something changes in other service they depend on they won’t notice it.
Consumer driven contract testing
When a consumer service uses a provider service, the provider should accept an agreed input and return an output with an expected format, so there is an implicit agreement (a contract) between both parts. We need a way to ensure it happens, and this should be driven by the consumer because is the one that would break in case of any change on the provider.
Which technologies can we use?
There are a few technologies that support it. Pact is one of the standard ones and has support for many programming languages (C#, Java, …) but to get most of it we should configure a separate server. If most of our projects are built with Spring and Java we can use Spring Cloud Contract that is easy to configure, is maintained by the company that maintains the Spring framework, has a great support from the community and it is free.
How can Spring Cloud Contract help us?
In two ways:
- On the producer side it generates automatically tests that check that the service works as expected, and generates and publishes Wiremock stubs that can be used by the consumer.
- On the consumer side it retrieves those stubs and allows to use them in our integration tests and in standalone tests that are focused on checking that the inputs and outputs work as expected.
How can we use it on the producer?
First we have to add a dependency to the contract verifier and configure a Maven plugin in which we will specify which is the base class for the tests. You can see an example of the pom.xml file here and of the class here. Then we just have to write a contract file like this one. Spring and Maven will do the magic generating the tests and the stubs as part of the package phase, running the test as part of the verify one and uploading the stubs to the artifact repository in the deploy stage.
And in the consumer?
We just have to add the dependency to the stub runner (example) and specify in the tests the group and artifact names of the provider (example). Spring will then download the latest version of the stubs from the artifact repository and use them like if we had defined them as Wiremock stubs.
And how can we detect a breaking change on the consumer?
With the previous settings you will be able to see if the communication has broken when you run the tests of the consumer, but the provider could be released separately by other team without letting us know, so we need a way to detect this.
In Jenkins we can configure the relation between projects so we can specify that the jobs of the tests of the consumers are triggered after the successful builds of the provider. This could trigger too many builds, so it is up to you to do it on the Dev, UAT and Prod environments or just on one of them
Can I run the contract tests separately from other types of tests?
Yes, you can do it configuring a Maven profile for them. You can see an example here.
Can I see a full example working?
You can find this example on GitHub in which I have defined a consumer (message-service) and a producer (uuid-service). This project also offers examples of other technologies such as service discovery so you can see how to use them together.