Contract Testing - When Integration Testing Becomes a Burden
Service A mocks Service B the old way, while Service B has changed its API. How can a 2-person team confidently deploy without fear of breaking each other?
In a Monolith, the compiler will report an error if you call a function incorrectly. In Microservices, your 'compiler' is the network calls, and it only reports an error when the system has already crashed in Production.
The Problem: The Obsolescence of Mocks
In post 7, we talked about using Mocks for local development. But there's a classic scenario:
- Service A (Consumer) uses a Mock of Service B to run tests. Tests pass with flying colors.
- Service B (Provider) changes the returned JSON structure (e.g., changing the
user_idfield toid). - Service A has no idea, still using the old Mock.
- You deploy both to Production and... BOOM! The system fails because Service A can't read the data from B anymore.
Traditional Integration Tests are often very slow and hard to maintain. We need a smarter solution.
1. What is Contract Testing?
Imagine a Contract as a formal agreement between two services.
- Consumer (A): "I need you to return data in this format."
- Provider (B): "I commit to always returning exactly that. If I want to change it, I must inform you."
Instead of running both services at the same time to test, we just need to check if each side complies with the "Contract" effectively "signed."
2. Pact - The Game-Changing Tool
We use Pact. The workflow is fascinating:
- Service A runs tests and generates a JSON file (the Contract).
- This file is pushed to a place called the Pact Broker.
- When Service B runs CI/CD, it downloads that file and verifies: "Does my actual data match what Service A expects?".
- If it doesn't match, Service B is not allowed to deploy.
3. Trade-off for Small Teams
The pain: Setting up a Pact Broker and writing Contract Tests takes extra effort. It requires a completely different mindset than writing normal Unit Tests.
The reward: Confidence. You can deploy Service B at 5 PM on a Friday knowing with 100% certainty that you aren't breaking Service A. For a small team, this confidence is invaluable because you don't have enough people to be on standby for 24/7 bug fixes.
Lessons Learned
- Consumer is the Power-holder: In Contract Testing, the service user (the Consumer) should be the one to set the requirements first.
- Minimize End-to-End (E2E) Testing: Don't try to write tests that stretch from the Frontend all the way to the final Database. They are very "flaky" and hard to debug. Contract Testing solves 80% of E2E problems much faster.
- Communication is Still Most Important: Tools are only aids. Always talk to your colleagues before making major changes to the API.
Conclusion
Contract Testing transforms "verbal" promises between developers into verifiable pieces of code. This is an important stepping stone for your small team to operate a system professionally and safely.
Series • Part 14 of 20