Microservices enable developers to scale, reduce blast radius, introduce changes quicker, and experiment faster. However, this comes at the cost of simplicity, manageability and testing scalability. Contract Testing is a methodology that helps bring order to the microservices chaos to create a healthy development process. Among other benefits, it also scales with your number of services. This post explains the challenges of testing microservices, what contract testing is, its advantages, and when to use it.
Microservices Testing Challenges
The top three challenges in testing microservices are:
Managing tests (and other artifacts) for a handful of services in traditional ways may still work. But when we talk about scaling to tens or hundreds of services, traditional testing can't keep up with the throughput of changes introduced and with business logic developments (writing new tests and adjusting old ones).
Keeping Up With Changes
Kubernetes (k8s) allows developers to manage the deployment of their packaged applications to deploy code faster. This leads to an enormous increase in changes in the application's functionality, which are all funneled to the integration environment to be tested.
Root Cause Analysis
When you find a bug, debugging it is really finding a needle in a haystack of changes. This directly affects your product quality, your repair time (MTTR), and the overall experience for your users.
These challenges make it very difficult to ensure code quality and quick releases. The solution? contract testing.
What is Contract Testing?
Contract testing is a methodology that gives every service a contract. The contract defines how a user (the Consumer) should work with the service, and which responses they should expect.
Typically, we address the service owner (a person or a team who is accountable for it) as the "Provider", and entities that require the service's interaction to fulfill their task as the "Consumer".
Consumers can be the actual person consuming resources through their mobile app connecting to a backend API-gateway or a Backend-for-frontend (BFF), or a service developed by a person that requires interaction from the Provider's service.
The contract should include three components: Documentation, Tests, and Mocks. I'll explain the concept of the "Service Contract" through an analogy of how one buys a house.
This is the Provider's service list of endpoints that are exposed to the Consumers of that service. The list defines the endpoints and their methods, the schema body, specific headers, and also the responses the Consumer should expect in return.
In our "Let's buy a house" example - This is the legal contract defining the house, its metrics (square ft., year it was built, no. of rooms, etc.), the transaction ($$$, dates, escrow), and consequences of delays (fines, losing the house) or any other contingency.
These are the Provider's service tests (simple functional, dependencies, flows, performance) the Provider exposes. They allow the Consumer to:
1. Speed up learning: Understand how the service will behave given a specific request (and what the valid response for it is) by using the tests and mocks to learn.
2. Experiment: Use the tests to initiate requests with different payloads to debug an issue.
3. Advanced case: Issue a Pull Request (PR) - if the Consumer can issue a PR, contract tests enable them to understand the tests' logic and extend them to cover the functionality that will make the Provider's life much easier after reviewing the PR.
4. Contract validation: The Provider can use the tests to "certify" new contracts by running the tests against the mock and against the real system to verify the responses match (schema and part of the response body).
In our "Let's buy a house" example - This is the House/Roof/Termites inspection teams that comes to the house you want to buy, and checks that everything is OK (square ft, year, rooms, but also - is there rust in your pipes? How are the foundations?). Their house inspection helps understand the state the house is in. For example, should you plan for a small remodel (refactor…) of your pipes because you want better water pressure?
This is the Provider's service mock. It enables you to code and test against it without deploying the real service(s). By virtualizing other microservices, integration points do not break and scaling is achieved. This also enables team independence.
In our "Let's buy a house" example - imagine 3D, VR-enabled house plans and permits (kind of like a "Google Earth meets Oculus Quest" of your house).
You can look around, open doors (if you have the right Key), and see the view. But, you can also try to break a wall and see if the house collapses, or if installing a high-pressure pump blows up your pipes!
Now ask yourself: if you have two houses that you want to place an offer for:
- House #1 - you get the full contract with the legal, the full testing, and the VR tour.
- House #2 - you get a legal contract only.
Which house would make you feel more comfortable and reassured with your choice?
If you picked House #1 - you are a Contract Testing believer and you are ready…
To sum up: Contract testing is a valuable solution for testing microservices, and specifically the integration points between them. Contract virtualization is the mocking of microservices based on their Contract documentation. It enables testing mocks instead of hitting the real services.
Five Contract Testing Advantages
With contracts, developers can quickly identify endpoints, understand requirements, and experiment with tests. Once the desired outcome is achieved, they can copy it to their code and continue the feature development using the data they received. This replaces lengthy processes, endless back-and-forths, and time spent learning the code.
In addition, contract testing enables quickly creating an isolated environment for code, instead of the long process of deploying a dev environment.
Reduced Infrastructure Cost
Developers working in a microservices environment have a lot of services. Code changes that interact with multiple services require them to deploy a dev environment to make sure their laptop doesn't crash and they don't disturb other team members.
Implementing contracts enables them to direct their code against the mocks, which are lightweight and create an isolated environment as code directly on their laptops. This will cut down on operational costs.(Goodbye "I forgot to descale the cluster!" or "Ahh! I forgot to take it down!")
Bonus: Since you can keep your old services revisions handy, you can test your new enhancements against other services' older versions, without the cost of additional environments, for backward compatibility.
Better Dev-to-Dev Communication
In the case of a bug, the Consumer can quickly send the payload and the test to the Publisher to be fixed (test + payload + mock = easy to reproduce). The Publisher (as the service owner) has everything they need in order to fix and produce a new contract version. Imagine working as a developer, and every time a Consumer found an error, they could reproduce it and point to exactly what went wrong. Then, you could make the fix and redeploy with no downtime, same day. Do you think that would make working with fellow devs easier?
Shorten the Feedback Loop
Developing and testing in an isolated environment instead of waiting for regression results enables developers to find functional and business flow issues quicker. The Provider can share negative tests and different Mock profiles (endurance, chaos, performance). Once the code is ready, they can test against these advanced scenarios locally and deal with issues on the spot... not after the regression suite finds them. If the code passes these tests, it is likely to pass regression tests, and get to production, saving future context switching.
Extreme Ownership and Accountability
You would probably feel uncomfortable buying a house with incorrect or missing specs in the contract, as compared to a house with a fully detailed contract, aligned correctly, with full transparency.
Similarly, contract testing empowers extreme ownership by causing Publishers to reflect their professionalism via their contract (check out Stripe API Docs - you'll get the feeling). Just think about the last time you integrated with a 3rd party and how their documentation (good or bad) reflected your delivery ETAs.
Bonus: Investing in a service contract will enable Consumers to become self-served. This will translate into more coding/coffee time for the Publisher. Either way, it's a Win-Win for everyone.
Four Contract Testing Use Cases
When should you use contract testing? Here are four scenarios.
New Project Onboarding
Developers onboarded to a new project can use contracts to quickly get up to speed. The contract will provide OpenAPI specs, quick and accessible explanations about the capabilities of the service, and its expected KPIs. The contract tests will help one understand the requests and responses, making it easier for the developer to understand the code (it's like assembling a puzzle when you already know the full picture).
Adding features to existing code requires compiling with Docker and the existing dependencies. This results in crashing computers. Developing in the cloud is resource-heavy; it entails developing an environment with Kubernetes. This is a very bulky process for adding small features.
With contract testing, developers receive the dependencies as a mock, which is lightweight. In addition, one Docker is enough for all dependencies and services if it contains the business logic. You can create your own private environment directly on your laptop... without making your computer become your new heater.
Contract testing validates the code according to all possible scenarios. Continuous testing of your code every time you release and deploy is important to ensure its quality. Therefore, continuous testing of the code contracts will assure changes in services and features for not break the architecture or integration points.
Testing that a new feature is compatible with previous versions of the other services can be nerve-wracking, especially in microservices where each service has its own versions. Contracts enable developers to easily search the service repository for the past three versions, run a Docker for all of them, and test.
I'm a believer! How do I do it?
That's why we started UP9 !
UP9 provides an out-of-the-box contract test automation for microservices, Kubernetes, and cloud-native systems, replacing the need for developers to constantly build and maintain tests, while providing comprehensive service test-coverage.
- Automatic generation and maintenance of CI-ready test-code, based on service traffic.
- Observability into API-contracts, business-logic and service architecture.
- Automatic reliability, test-coverage and root-cause analysis.
- Machine generated tests include functional, regression, performance and edge-case test-cases, covering all services and all service-endpoints.
UP9 offloads the microservices contract testing workload from developers, giving them precious time back. Sign up and start contract testing for free .
Join our webinar about contract testing:
"How Contract Testing Helped Cycode Prevent Software Regressions in their Kubernetes and Kafka Environments". Feb. 17th 11:30 ET / 18:30 Israel.
Sign up now.