Contract Testing with Fake Data: Pact and OpenAPI Mocking Explained
Regular API mocking has a blind spot: your frontend tests pass against a mock you wrote yourself, but nothing verifies that mock actually matches what the real backend returns. Contract testing closes that gap — it checks that both sides of an API integration agree on the same shape, using the fake data itself as the enforceable contract.
Why Mocking Alone Isn't Enough
If your frontend mocks `GET /orders/:id` returning `{ id, total, status }`, but the backend team renames `status` to `orderStatus` next sprint, your mocked tests keep passing — they're testing against your assumption, not reality. Contract testing catches that drift before it reaches production.
Approach 1: Pact (Consumer-Driven Contracts)
Pact works by having the frontend ("consumer") define what it expects from an endpoint using fake data, generating a "pact file" from that expectation, then replaying those exact requests against the real backend ("provider") in a separate verification step.
// consumer test
import { PactV3, MatchersV3 } from '@pact-foundation/pact'
const { like } = MatchersV3
provider.addInteraction({
states: [{ description: 'an order with id 101 exists' }],
uponReceiving: 'a request for order 101',
withRequest: { method: 'GET', path: '/orders/101' },
willRespondWith: {
status: 200,
body: like({ id: 101, total: 42.5, status: 'shipped' }),
},
})The like() matcher generated from your fake data tells Pact "match the type and shape, not the exact value" — so the contract stays valid even as the actual numbers change between runs.
On the backend side, the provider team runs a verification step that replays the exact same request against the real API and checks the response matches the contract. If someone renames a field, this step fails immediately — in CI, before either team ships.
Approach 2: OpenAPI-Driven Mocking (Prism)
If your team already maintains an OpenAPI spec, a lighter-weight alternative is generating a mock server directly from that spec using a tool like Prism. Because the mock is generated from the same contract the real API is supposed to implement, any drift between the spec and the real implementation is a documentation problem you can catch by validating both against the same spec — rather than a full separate contract-testing pipeline.
npx @stoplight/prism mock openapi.yaml
# serves realistic example responses based on your schema's examplesPrism uses the example fields inside your OpenAPI spec to generate responses — which means the quality of your mock depends entirely on how realistic those examples are. This is where generating good example values up front, with a tool like Dummy JSON Generator, pays off — vague placeholder examples like "string" produce useless mocks, while realistic examples produce a mock server that's actually usable for frontend development.
Pact vs OpenAPI Mocking: Which to Use
| Situation | Recommended Approach |
|---|---|
| Frontend and backend are separate teams/repos | Pact — enforces the contract across repo boundaries in CI |
| You already maintain an OpenAPI spec | Prism — reuses the spec you already have |
| Small team, one repo, moving fast | Plain MSW mocking is usually enough — Pact's overhead isn't worth it yet |
| Multiple frontend clients consuming the same backend | Pact — one contract, verified against every consumer |
The Bottom Line
Contract testing is worth the setup cost once more than one team owns different sides of an API boundary — the fake data you'd already be generating for mocks becomes the enforceable contract instead of just a local convenience. For a single small team, plain mocking is usually sufficient; Pact and OpenAPI-driven mocking earn their keep as the number of consumers and teams grows.