Exploring consume driven contract testing
Testing an application requires effort and time to do it “properly”, for example, in Succeeding with Agile by Mike Cohn, the pyramid of tests is a guide to understand what amount of tests we should have in an application. Despite the pyramid having no numbers, the proportion is what we should focus on. however, Cohn didn’t mention other types s tests that could be beneficial for developers to have in place. (Ayas et al., 2022) found that using contract testing showed that other types of tests were also in place.
Contract testing is a way to unload the heaviness of integration and even end-to-end tests to give developers faster feedback (Corporation, 2022).
In this post, I am going to explore a bit about what is Contract Testing (CT) and what it means for developers to adopt such a strategy.
What is Contract Testing?
Contract Testing is a testing strategy that focuses on the contract between two services, rather than the implementation details of the services themselves. It allows developers to define a contract that specifies how the services will communicate with each other, and then validate that the contract is being followed by both parties. Contract Testing can be seen as a way to decouple services, as it allows them to evolve independently, as long as they adhere to the agreed-upon contract. This can lead to more flexible and maintainable systems. Contract Testing is often used in microservices architectures, where services need to communicate with each other to fulfill business needs. It can also be used in monolithic applications, where different parts of the application need to communicate with each other.
The need for a contract
Often applications do not survive in isolation, they need to communicate with other services to fulfill business needs. This communication can be done in many ways, such as HTTP, gRPC, or even message brokers. The important part is that the communication is done through a contract, which is an agreement between the two parties on how they will communicate. This contract can be defined in many ways, such as OpenAPI, AsyncAPI, or even a simple JSON schema. The important part is that the contract is agreed upon by both parties and is used to validate the communication between them.
When we talk about Contract Testing, we are talking about testing the contract between two services. This means that we are testing if the contract is being followed by both parties and if the communication is working as expected. This is important because it allows us to catch issues early in the development process, before they become a problem in production. It also allows us to have a clear understanding of how the services communicate with each other, which can help us to avoid breaking changes in the future.
Solutions out there
There are several tools and libraries available for Contract Testing, such as:
- Pact: A popular library for Contract Testing that allows developers to define contracts in a simple and easy-to-understand way. Pact supports multiple languages and frameworks, making it a versatile choice for many projects.
- Spring Cloud Contract: A library for Contract Testing that is part of the Spring ecosystem. It allows developers to define contracts using Groovy or YAML, and provides support for both consumer-driven and provider-driven contracts.
- WireMock: A library for mocking HTTP services that can also be used for Contract Testing. WireMock allows developers to define contracts using JSON or Java, and provides support for both consumer-driven and provider-driven contracts.
- Hoverfly: A lightweight API simulation tool that can be used for Contract Testing. Hoverfly allows developers to define contracts using JSON or YAML, and provides support for both consumer-driven and provider-driven contracts.
- MockServer: A library for mocking HTTP services that can also be used for Contract Testing. MockServer allows developers to define contracts using JSON or Java, and provides support for both consumer-driven and provider-driven contracts.
These tools and libraries provide a way for developers to define contracts in a simple and easy-to-understand way, and then validate that the contract is being followed by both parties. They also provide support for both consumer-driven and provider-driven contracts, which allows developers to choose the approach that best fits their needs. However, each one of those are for a specific scenario. For example, WireMock is used to mock HTTP services, while Pact is a more general-purpose library that can be used for any type of service communication.
Practical example
Let’s say we have a service that provides a list of users, and another service that consumes this list to display it in a UI. The contract between these two services could be defined as follows:
{
"type": "object",
"properties": {
"users": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" }
},
"required": ["id", "name"]
}
}
},
"required": ["users"]
}
This contract defines that the service providing the list of users must return an object with a property called
users
, which is an array of objects. Each object in the array must have an id
(integer) and a name
(string).
When the consumer service (the one displaying the users) makes a request to the provider service, it can validate the
response against this contract. If the response does not match the contract, the consumer service can fail fast,
alerting developers to the issue immediately. This way, we can ensure that the provider service is always returning
the expected data, and the consumer service can rely on this contract to function correctly.
Benefits of Contract Testing
Contract Testing provides several benefits for developers and teams:
- Early Detection of Issues: By validating the contract between services, developers can catch issues early in the development process, reducing the risk of breaking changes in production.
- Clear Communication: Contract Testing provides a clear understanding of how services communicate with each other, which can help teams to avoid misunderstandings and miscommunications.
- Faster Feedback: Contract Testing allows developers to get faster feedback on their changes, as they can validate the contract without having to run full integration or end-to-end tests.
- Decoupling Services: Contract Testing allows services to evolve independently, as long as they adhere to the agreed-upon contract. This can lead to more flexible and maintainable systems.
- Improved Collaboration: Contract Testing encourages collaboration between teams, as they need to agree on the contract and ensure that it is followed by both parties.
Resources
- Consumer-driven Contract Testing (CDC)
- Spock Primer
- Evolving a Service: An Example
- Testing in microservice systems: a repository mining study on open-source systems using contract testing
- Contract-driven development - #SCOPUS 2007
- Improving Object-Oriented Software Contracts - May 2011
References
- Ayas, H. M., Fischer, H., Leitner, P., & Neto, F. G. D. O. (2022). An empirical analysis of microservices systems using consumer-driven contract testing. 2022 48th Euromicro Conference on Software Engineering and Advanced Applications (SEAA), 92–99.
- Corporation, M. (2022). Consumer-driven Contract Testing (CDC). https://microsoft.github.io/code-with-engineering-playbook/automated-testing/cdc-testing/