Contract Testing

API Testingadvanced

// Definition

Verifying that two services agree on the shape of the messages they exchange. Catches breaking API changes without expensive end-to-end tests across multiple deployed services.

// Why it matters

Contract testing verifies that a provider and consumer agree on the shape of their API — without spinning up both. It catches the integration break that unit tests miss and E2E tests catch too late/slowly. QA value is speed + precision: a contract failure tells you exactly which field changed, in CI, before deploy.

// How to test

// Consumer-side: assert the response matches the agreed schema
cy.request('/api/users/1').then((res) => {
  expect(res.body).to.have.all.keys('id', 'name', 'email', 'role')
  expect(res.body.id).to.be.a('number')
  expect(res.body.role).to.be.oneOf(['admin', 'editor', 'viewer'])
})
// Dedicated tools (Pact) formalise this as a shared contract both sides verify.

// Code Example

PactConsumer-driven contract test
TypeScript
import { PactV3, MatchersV3 } from '@pact-foundation/pact';

const provider = new PactV3({ consumer: 'web', provider: 'api' });

provider
  .uponReceiving('a request for tools')
  .withRequest({ method: 'GET', path: '/tools' })
  .willRespondWith({
    status: 200,
    body: MatchersV3.eachLike({ id: 'cypress', name: 'Cypress' }),
  });

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

// Common mistakes

  • Asserting only the fields you use, missing a removed field others need
  • Treating contract tests as a replacement for, not a complement to, E2E
  • No shared source of truth, so provider and consumer drift apart

// Related terms

Learn more · API Testing Masterclass

Chapter 7 · Lesson 1: What Is Contract Testing and Why It Matters