CONCEPT Cited by 1 source
Contract testing¶
Definition¶
Contract testing verifies an API contract from both sides: the consumer writes tests against its own expectations of the provider's schema, and the provider replays those expectations against its real implementation. Both sides run the tests in CI. A change on either side that breaks the shared contract fails its own CI, catching cross-service drift before it reaches production.
The two mainstream flavours:
- Consumer-Driven Contract (CDC) — consumers publish expectations (example request + expected response). The provider retrieves those and verifies against its real server. Tooling: Pact, Spring Cloud Contract.
- Provider-contract: provider publishes an OpenAPI / protobuf / AsyncAPI schema; consumers verify their mocks and generated clients agree.
What it solves that Testcontainers-based ITs don't¶
A Testcontainers integration test that uses systems/mockserver or systems/wiremock to stand in for an external HTTP peer tests the wiring end of the boundary: our code can make the call, handle the response, retry on 5xx, time out correctly. It does not test that the mock matches the real peer's current behaviour.
Consequence: the real peer changes a field name, adds a required header, tightens validation. Our ITs stay green because the mock hasn't been updated. Production breaks on deploy.
Zalando names this explicitly: "while testing REST responses with a mockserver container you can miss changes of real API. Inside the integration test, you may not reflect it, and your code still can crash on production. To minimize the risk, you may consider leveraging Contract Testing via Spring Cloud Contract." (sources/2021-02-24-zalando-integration-tests-with-testcontainers)
Where contract testing sits in the pyramid¶
Contract tests are a diagonal cut through the pyramid, not a layer. On the consumer side they're roughly unit-test altitude (fast, isolated, run against a mock generated from the contract). On the provider side they're roughly integration-test altitude (run the real server, replay consumer expectations).
What makes them different from regular ITs: the provider side doesn't write the tests — consumers do — so coverage reflects how the API is actually used, not what the provider thinks consumers might do.
Common tooling¶
- Pact — language-agnostic; consumers write pacts, publish to a Pact Broker, providers verify. Large ecosystem (Ruby, JS, JVM, .NET, Go, Python, Rust).
- Spring Cloud Contract — JVM-native; producers write Groovy/YAML contracts in their repo, generate WireMock stubs for consumers and test classes for themselves. Tight Spring Boot integration.
- Postman / Bruno / OpenAPI-based tooling — lighter-weight schema-only checks; doesn't enforce example-level request/response matching.
Failure modes¶
- Contracts drift from reality — provider changes its API without updating contracts; consumers' pacts still pass. Mitigation: require contract update as part of the API PR, enforce with a lint gate.
- Over-specification — pacts pin fields that the consumer doesn't actually care about. Provider evolves, consumer's pact breaks, but consumer was fine. Requires discipline: pacts should reflect what the consumer reads, not the full response shape.
- Broker as single point of failure — Pact Broker / the team's contract registry being down blocks CI. Usually solved with contract artefacts pinned per build.
Seen in¶
- sources/2021-02-24-zalando-integration-tests-with-testcontainers — Zalando ZMS explicitly names contract testing (Spring Cloud Contract) as the complement Testcontainers alone doesn't provide. Canonical framing of Testcontainers + contract testing as non-overlapping test coverage for the same external-boundary risk.
Related¶
- systems/spring-boot — Spring Cloud Contract's runtime context.
- concepts/test-pyramid — diagonal cut through, not a single layer.
- patterns/real-docker-container-over-in-memory-fake — the coverage gap contract testing fills.