Contract testing

The number one risk of a published spec is drift. Someone changes a handler, forgets to update the spec, and now the two disagree. Clients generated from the spec break against the live API. Contract tests are how you catch drift automatically, in CI, before it hits customers.

tests/contract.test.ts
typescript
import Ajv from 'ajv';
import request from 'supertest';
import spec from '../openapi.json';
import { app } from '../after/index';

const ajv = new Ajv();

describe('API contract', () => {
  it('GET /api/v1/books matches the OpenAPI schema', async () => {
    const res = await request(app).get('/api/v1/books');
    expect(res.status).toBe(200);

    // Compile the schema straight from the published spec
    const validate = ajv.compile(spec.components.schemas.BookListResponse);
    const valid = validate(res.body);

    expect(validate.errors ?? []).toEqual([]);
    expect(valid).toBe(true);
  });
});

Hit the endpoint, pull the response schema out of the spec, and validate the real body against it with Ajv.

Two complementary checks to add in CI. One, validate every response against the spec on every test run. Two, diff the current spec against the one on main and flag breaking changes for review. Together, they make drift almost impossible and breaking changes impossible to miss.

Quiz: Quiz

Loading practice…