Microservices provide a degree of granularity and flexibility that allows projects to scale the service with ease. When a project is built with a lower number of microservices, it is easy to communicate with the provider on the changes to the specific microservice. Whereas once the project is using a large number of microservices (have a look at Amazon and Netflix’s services), this becomes more complicated for example if one of the services changes the structure of the output response or the structure of the request body.
Here is where Contract Testing comes into the picture. Looking at the Test Pyramid, Contract testing fits very well at the service layer as they execute quickly and don’t need to integrate to external systems to run. Their job is to give you confidence that the systems you integrate with are compatible with your code before you release it.
When it comes to End to End testing, there are some challenges:
- E2E tests are not that quick since they talk to multiple systems and verify that the system as a whole meets the business needs.
- The system is treated as a black box and the tests exercise as much of the fully deployed system as possible, manipulating it through public interfaces such as GUIs and service APIs and each test may take several seconds to give feedback.
- Not always easy to maintain – E2E tests would need the system to be in the right state and also the right version and data.
- Flakiness – E2E tests might end up with false-positives because of the complexity in the test environment. In many cases, they might fail due to incorrect configuration of the environment or data.
Moving tests to the service layer can help with the challenges listed above, and the way to go for this is with contract testing as it is very fast, runs against stub, easy to maintain, debug, fix and scale.
In this guide, we will review how to practically adopt the Consumer-Driven Contracts (CDC) pattern to address this situation and deploy with confidence, while also integrating CDC as part of CI/CD.
Table of Contents – The Ultimate Guide to Testing Microservices
- You’re here → Introduction to Consumer Contract Testing
- Consumer-Driven Contract Testing using Pact.js
- Consumer-Driven Contract Testing using Pact Java
- Consumer-Driven Contract Testing using Spring Cloud Contracts
- Event Driven Architecture: How to Perform Contract Testing in Kafka/pubSub
- Integrating Contract Testing in Build Pipelines
What is Contract Testing?
Contract testing is writing tests to make sure the services can communicate well with each other. There are two perspectives in Contract testing: One is the consumer entity using the service and the other is the provider entity who provides the service. As an example, these two parties can be a Frontend and Backend, or two Backend services integrating with each other.
Contract tests check the contract of external service calls, but not necessarily the exact data. Often a stub will snapshot a response at a particular date, since it’s the format of the data matters rather than the actual data. In this case, the contract test needs to check that the format is the same, even if the actual data has changed.
In our example used in the next chapters, we can say that the set of assertions generated by all consumers, expresses the mandatory structure of the messages to be exchanged during the period in which the assertions remain valid for their parent applications. If the provider were possessed of this set of assertions, it would be able to ensure that every message it sends is valid for every consumer insofar as the set of assertions is valid and complete.
Generalizing this structure, we can distinguish what we have already called the provider contract from the individual contractual obligations that obtain in instances of provider-consumer relationships, which we will now call consumer contracts. When a provider accepts and adopts the reasonable expectations expressed by a consumer, it enters into a consumer contract.
Whilst contract tests provide confidence for consumers of external services, they are even more valuable to the maintainers of those services. By receiving contract test suites from all consumers of a service, it is possible to make changes to that service safe in the knowledge that consumers won’t be impacted.
Consider the provider exposes three attributes as id, name and age and this service is been consumed by three different consumers.
- Consumer A only requires id and name attribute and not age, in such case the contract tests only assert these two attributes and ignore age attribute.
- Consumer B only requires id and age attribute and not the name, in such case the contract tests only assert these two attributes and ignore name attribute.
- Consumer C requires all three fields and has a contract test suite that asserts they are all present.
If a new consumer adopts the API but requires both firstName and lastName, the provider removes the name and introduces another field as first and last name. Now when the provider runs the contract tests, the test would fail as Consumer A and Consumer C require a name field. In this case, the provider marks the name as deprecated and after Consumer A and C are migrated to the new field, the deprecated field is removed.
These consumer driven contracts form a discussion point with the team responsible for building the service as well as being automated tests that give an indication of readiness of the API.
Happy Testing 😉
Sai Krishna & Srinivasan Sekar