Skip to main content

Defining a ContractCase Example

In ContractCase, a contract is a series of examples. Each example is independent - for example: "a getUser request looks like $X, and a success response looks like $Y":

import { willSendHttpRequest } from '@contract-case/contract-case-jest';

await contract.runExample({
definition: willSendHttpRequest({
request: {
method: 'GET',
path: '/users/foo',
},
response: {
status: 200,
body: {
// Note that we only describe the fields
// that your consumer actually needs for
// this particular test. The real response
// might have more elements, but if your
// consumer doesn't need them, you don't
// need to put them in the contract.
userId: 'foo',
name: 'john smith',
},
},
}),
});

Examples should be independent

Instead of making examples dependent on each other (and the corresponding explosion of complexity and potentially brittle tests), each contract definition example runs independently.

This is achieved with state definitions and state setup handlers, which you can use to set up any preconditions that your example needs (for example, a user existing) required by the getUser request first.

Any preconditions needed to set up the examples are handled with state - for example: "when a user with ID 'foo' exists, a getUser request looks like $X, and a success response looks like $Y":

import {
willSendHttpRequest,
inState,
stateVariable,
stringPrefix,
anyString,
} from '@contract-case/contract-case-jest';


it('behaves as expected', async () => {
await contract.runExample({
states: [
inState('Server is up'),
inState('A user with id "foo" exists'),
],
definition: willSendHttpRequest({
request: {
method: 'GET',
path: '/users/foo',
},
response: {
status: 200,
body: {
userId: 'foo',
name: 'john smith',
},
},
}),
});
});

See the state handlers section for more on state handlers.

Overriding configuration

You can override the configuration for individual tests by providing an optional ContractCaseConfig object as the final argument. See the configuration options reference for full details. For example, here's how you could set the log level to debug for an individual test:

        await contract.runExample(
{
states: [
/* as above */
],
definition:
/* as above */
},
{ logLevel: 'debug' },
);

Setting the log level to debug is a useful way to see what is happening during the test, which can help you understand why an example might be failing.

How do I tell ContractCase that a field is optional?

Instead of marking fields as optional, write two contract tests, one with the field and one without. To provide the best deployment confidence, a contract test needs specific examples, so there is no optional matcher.

This is because a test which allows optional fields could still pass even if the field was never present. You wouldn't be sure that your provider could ever generate that missing field.

Do I describe the whole API payload?

You only need to describe the parts of the API payload that your consumer relies on.

This is advantageous because it's not a breaking change to rename or remove a field/endpoint that no-one is using.

Next steps

Next, set up your state state definitions.