TestingAPIs is still being built. If something breaks, email support@testingapis.com.

What contract testing is

The agreement between an API and its clients, the bugs that break it, and how contract tests differ from the rest.

An API is useful only because other programs rely on it. A web shop's frontend expects GET /api/Products/1 to return an object with a pricethat is a number. A mobile app expects a 404 for a product that doesn't exist, with a message it can show. Those expectations, taken together, are a contract between the API and the programs that use it. Contract testing checks that both sides still keep it. This lesson explains what that means and why ordinary tests miss the bugs contract tests catch.

Running the examples

The tutorial's code is plain JavaScript with no libraries, so every line is readable, including the validator lesson 3 builds. You need Node.js 22 or later. Download the project, unzip it and run:

Terminal
$ node --version
v24.14.1

$ cd contract-testing
$ npm test
...
ℹ tests 44
ℹ pass 44
ℹ fail 0

The tests read the API's published OpenAPI document and call the API, and only one of them changes anything: it creates a todo and deletes it again.

Provider and consumer

Contract testing has its own two words for the two sides:

  • The provider is the API: here, api.testingapis.com.
  • A consumeris any program that calls it: a web frontend, a mobile app, another backend service, a partner's integration, or your own test suite.

A contract covers everything a consumer could reasonably depend on:

Part of the contractExample
Which endpoints exist, with which methodsGET /api/Products/{id}
Parameters and their typesid is an integer, in the path
Request bodies: fields, types, which are requiredCreating a product needs a title
Status codes200 for a product, 404 when there isn't one
Response fields, their types, and whether they're always presentprice is a number, always there
Error bodies{ status, error, message }
Content typesapplication/json

Why ordinary tests miss contract breaks

Here is how a contract usually breaks:

A typical week
Monday     The backend team renames a field in GET /api/Products/{id}:  inStock → available
           Their tests pass: they updated them along with the code.
Tuesday    The release goes out.
Tuesday    The shop's product page reads product.inStock, gets undefined,
           and shows every product as sold out.
Wednesday  Someone notices the sales graph.

Nobody did anything careless. The backend's tests were changed along with the code, which is what good tests are supposed to do, and so they passed. The frontend's tests use a fake API that still returns inStock, so they passed too. Each side was tested against its own idea of the API, and the two ideas had quietly separated. The bug lived in the gap between them.

A contract test closes that gap by checking both sides against one shared description of the API. If the backend renames inStock, a contract test fails before the release, because the contract still says inStock. The team then decides deliberately: keep the old name, add the new one alongside it, or agree a change with every consumer.

How contract tests differ from other API tests

TestAsksExample
FunctionalDoes the API do the right thing?Product 1 is the headphones and costs 249.99
ContractDoes the API still have the promised shape?Every product has a numeric price, and nothing unexpected
End to endDoes the whole system work together?A user can find a product and buy it

Contract tests check shapes, not values. They don't care that product 1 costs 249.99, only that priceis there and is a number. That makes them fast, and stable when the data changes, but it also means they don't replace functional tests. A price that is a number and wrong passes every contract test.

Two ways to write the contract down

  • Provider-driven. The provider publishes a description of its whole API, usually an OpenAPI document, and that file is the contract. Tests check the real API against the file. Lessons 2 to 6 work this way.
  • Consumer-driven. Each consumer writes down the requests it makes and the fields it actually uses, and the provider checks it still meets them all. A change no consumer depends on is free to make. Lesson 7 covers this.

Most teams that do contract testing well use both, and lesson 8 runs them in CI.

A contract that said almost nothing

A contract only protects what it describes. When work on this tutorial began, this API's OpenAPI document listed every endpoint, but here is how many of them it said anything about the response for:

Terminal
$ node demos/spec-coverage.mjs        # the spec as it was when this tutorial began
spec:        https://api.testingapis.com/openapi/v1.json (OpenAPI 3.0.4)
operations:  400
described:   1 have a schema for their success response
             GET /WeatherForecast
This example uses endpoints for signed-in members.Sign inCreate a free account

One out of 400, and that one was a leftover from a project template. The schemas existed, but no response pointed to them, because the code returned results the documentation generator couldn't see into. A consumer could rely on the spec for which endpoints existed and nothing about what came back. So the first contract test this tutorial ran found its first bug: the contract itself was too weak to test against. The API was fixed for the products, posts and todos resources, which the rest of the tutorial uses:

Terminal
$ node demos/spec-coverage.mjs        # after the fix
spec:        https://api.testingapis.com/openapi/v1.json (OpenAPI 3.0.4)
operations:  400
described:   20 have a schema for their success response
             GET /api/Products
             POST /api/Products
             DELETE /api/Products/{id}
             GET /api/Products/{id}
             PUT /api/Products/{id}
             ... and 15 more
This example uses endpoints for signed-in members.Sign inCreate a free account

Check your understanding

  1. 1.In contract testing, which side is the provider and which is the consumer?Show answer

    The provider is the API. A consumer is any program that calls it, including a test suite.

  2. 2.Both teams' tests passed, yet the release broke the product page. How?Show answer

    Each side was tested against its own idea of the API: the backend updated its tests with the rename, and the frontend tested against a fake API with the old field. Nothing checked the two against a shared contract.

  3. 3.A product's price comes back as 2.49 instead of 249.99. Does a contract test catch it?Show answer

    No. The shape is still right: price is a number. That's a job for a functional test.

  4. 4.What's the difference between provider-driven and consumer-driven contracts?Show answer

    In provider-driven testing, the provider's published spec is the contract. In consumer-driven testing, each consumer states what it uses, and the provider checks it meets all of them.

Exercise

Pick an API you use at work or in a project, and list five things your code depends on that a change could break: a field name, a type, a status code, an error format. For each, find out whether the API's documentation promises it, or whether you're relying on behavior nobody wrote down. Then run node demos/spec-coverage.mjs in the example project with BASE_URLpointing at another API's server, if it publishes its spec at /openapi/v1.jsonsign in to see.

Practise and go further