Contract tests
The Mangrove app and the order service ship on different days, and old app versions stay on customers' phones for months. Contract tests check that every version still in use can talk to the next build of the service, in seconds, before anything reaches production.
By the end you can
- Explain why unit, API and end-to-end tests all miss a field renamed between two separately released systems
- Write a Pact consumer test for
GET /orders/:idthat produces a pact file, and verify the order service against it with provider states - Use a Pact Broker, git commit versions, environments and
can-i-deployto block a release that would break a supported app version - Classify API changes as breaking or safe, and ship a breaking one with expand and contract
- Decide when consumer-driven contracts are worth it, and when an OpenAPI diff or an end-to-end test is enough
The everyday picture
A rice supplier delivers to all 320 Mangrove stores. At every receiving door, staff scan the barcode printed on the top of each sack, because that is how the handheld scanners are set up. One month the supplier redesigns the sack and moves the barcode to the side. The supplier's own checks all pass. At the stores, receiving slows to a crawl, because nobody told the supplier that the top barcode was the one thing the stores relied on.
Now imagine the stores had written it down: "we scan a barcode on the top; we read the weight from the label; we ignore everything else." The supplier checks every new sack design against that short list before printing a single one. That list is a contract written by the people who use the thing: short, limited to what they really depend on, and something the supplier can test on their own without driving a truck to 320 stores. Contract tests give two pieces of software the same deal.
Why the app and the service break each other
The Mangrove app (React Native) and the order service (NestJS and PostgreSQL) are owned by different teams and released on different schedules. The order service deploys several times a week. A new app version reaches the app stores about every two weeks, and the old versions do not go away, because many customers turn off automatic updates. At Mangrove, the app analytics usually show seven or eight versions placing orders at the same time, and the oldest supported one is about four months old.
So every deploy of the order service has to work with every app version still in pockets. Look at what each level from A test strategy, not a pile of scripts can see. Unit tests check price rules with no app at all. API tests (Unit and API tests) call the service the way its own developers think it is called, and a developer who renames a field updates them in the same pull request. End-to-end tests (End-to-end tests with Playwright) run the newest app against the newest service. Nobody runs app 4.6 against tomorrow's service build. That pairing is exactly where the bug lives.
Running eight old app builds through end-to-end tests would take hours and still miss screens nobody scripted. A contract test checks the one thing that matters at the boundary, the shape of the requests and responses, and does it for each side on its own, in seconds.
Quick check
A developer renames a response field, updates the service's API tests and the newest app in the same sprint, and every pipeline is green. Why did no test catch the break?
Every test that ran used the new name on both sides, so they all agreed with each other. The broken pair, an old app build against the new service, was never tested. End-to-end tests can see the field, but they only run the newest app.
GET /orders/:id on today's service build. The end-to-end suite only pairs the newest app with the newest service, so a renamed field breaks 5.2, 5.0 and 4.6 without a single red test.