Skip to main content
Back to Practice API
Contract Testing Lab

Detect Breaking Changes

Contract testing prevents "works on my service, breaks in yours." Compare OpenAPI v1 vs v2 of the Practice API, then see how Pact, Schemathesis, and openapi-diff surface breaking changes before they ship.

Live specs to compare

  • GET /api/openapi?version=v1 โ€” original spec (stable contract)
  • GET /api/openapi?version=v2 โ€” evolved spec with breaking changes
  • GET /api/openapi?version=full โ€” current full spec (all endpoints)

4 classes of breaking changes

Removed field

GET /api/v1/users/:id
before:
{ id, name, email, username }
after:
{ id, name, email }

Consumers reading user.username break silently until runtime.

Renamed field

GET /api/v1/posts/:id
before:
{ id, title, body }
after:
{ id, title, content }

Rename-only refactors can be detected via OpenAPI diff before deploy.

Required โ†’ optional

POST /api/v1/users
before:
email is required
after:
email is optional

Provider relaxation. Usually safe, but consumer validators may now accept invalid data.

Type change

GET /api/v1/products/:id
before:
price: number
after:
price: string ("9.99 EUR")

Hard break. Consumer math on price silently fails at runtime.

Three tools, three angles

Pact (consumer-driven contracts)

The consumer defines expectations. The provider later verifies those expectations hold. Best for tightly-coupled teams.

import { PactV3, MatchersV3 } from '@pact-foundation/pact';

const provider = new PactV3({
  consumer: 'WebApp',
  provider: 'PracticeAPI',
});

describe('GET /api/v1/users/:id', () => {
  it('returns a user with id, name, email', async () => {
    provider
      .given('user 1 exists')
      .uponReceiving('a request for user 1')
      .withRequest({ method: 'GET', path: '/api/v1/users/1' })
      .willRespondWith({
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: {
          id: MatchersV3.like('1'),
          name: MatchersV3.string('Alice'),
          email: MatchersV3.email('alice@example.com'),
        },
      });

    await provider.executeTest(async (mock) => {
      const res = await fetch(`${mock.url}/api/v1/users/1`);
      expect(res.status).toBe(200);
    });
  });
});

Schemathesis (property-based fuzzing)

Reads your OpenAPI spec, generates thousands of requests, finds edge cases the spec allows but your server rejects.

# Install: pip install schemathesis
# Property-based tests against the live API using the OpenAPI spec.

schemathesis run \
  --url https://api.qa-practice.dev \
  https://api.qa-practice.dev/api/openapi?version=v2 \
  --checks all \
  --hypothesis-max-examples=100

openapi-diff (static breaking-change detection)

CI-friendly diff between two OpenAPI specs. Zero runtime cost, catches most structural breaks before deploy.

# Detect breaking changes between v1 and v2 from CI:
npx openapi-diff \
  https://api.qa-practice.dev/api/openapi?version=v1 \
  https://api.qa-practice.dev/api/openapi?version=v2

# Non-zero exit = breaking changes detected. Fail the PR.

Related Labs