API Contract Testing
debt(d5/e5/b5/t5)
Closest to 'specialist tool catches it' (d5). The absence of contract testing is detectable by specialist tools like Pact, Dredd, OpenAPI-validator, and Spectral, which can identify missing or broken contracts. However, the lack of contract tests isn't caught by default linters or compilers — you need deliberate adoption of these specialized tools. Without them, breaking API changes are silent until deployment or production (d7-d9), but with the listed tools in CI it drops to d5.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix suggests adopting Pact for consumer-driven contract testing, but this isn't a one-line fix. It requires writing consumer-side expectations, setting up provider-side verification, potentially deploying a Pact Broker for team sharing, and integrating into CI pipelines. This touches multiple files across consumer and provider codebases, making it a significant but bounded refactor — not quite architectural rework (e7) but more than a simple parameterized fix (e3).
Closest to 'persistent productivity tax' (b5). Contract testing applies to web/api contexts across microservices and imposes ongoing maintenance: contracts must be updated as APIs evolve, a Pact Broker needs to be maintained, and every new consumer-provider relationship requires new contracts. Tagged as microservices/api-design, this is a cross-cutting concern that affects multiple teams' workflows. It's not quite b7 (doesn't shape every change) but definitely more than b3 (affects multiple work streams, not just one component).
Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception field states developers believe E2E tests are better than contract tests, leading them to skip contract testing entirely in favor of slow, flaky integration tests. Common mistakes include provider-driven contracts (instead of consumer-driven), overly specific contracts testing entire response structures, and not running contracts in CI. These are well-documented gotchas that experienced developers learn, but they contradict the intuition that more comprehensive tests (E2E) are always better.
Also Known As
TL;DR
Explanation
Consumer-driven contract testing (Pact): the consumer defines a contract (the requests it makes and responses it expects), the provider verifies it can fulfil those contracts independently. Unlike integration tests, both sides run independently — no shared environment needed. The Pact Broker stores and shares contracts between teams. Benefits: catches breaking API changes in CI without a shared staging environment, enables independent service deployments, and documents the actual consumer expectations. PHP: pact-foundation/pact-php, pest-pact.
Common Misconception
Why It Matters
Common Mistakes
- Provider-driven contracts — the provider dictates what it sends, not what consumers need.
- Not running contract tests in CI — contracts only help if violations are caught automatically.
- Overly specific contracts — test the fields you use, not the entire response structure.
- No Pact Broker — contracts stored locally cannot be shared between teams.
Avoid When
- A single team owns both consumer and provider and deploys them together — integration tests are simpler.
- The API surface changes so frequently that maintaining contracts costs more than the bugs they prevent.
- Simple internal SDKs where the provider and consumer share the same codebase and test suite.
When To Use
- Services owned by different teams or deployed independently — contract tests catch provider changes before they reach staging.
- Public or partner APIs where breaking changes have downstream cost and manual coordination is slow.
- CI pipelines between microservices to get fast feedback without spinning up the full environment.
Code Examples
// No contract testing — breaking changes discovered in staging:
// Provider renames 'user_email' to 'email' in v2
// Consumer teams never notified
// Integration test catches it... in a 2-hour E2E test run
// Fix: roll back provider deployment, coordinate change
// Cost: 4 hours of engineer time
// Pact consumer test:
$builder->given('user 42 exists')
->uponReceiving('a request for user 42')
->with(['method' => 'GET', 'path' => '/users/42'])
->willRespondWith([
'status' => 200,
'body' => ['id' => 42, 'email' => 'alice@example.com'],
]);
// Provider verification runs against actual implementation:
$verifier->verify(); // Fails if provider returns user_email not email
// Caught in provider's CI — before deployment
// Fix: coordinate field rename with consumer teams first