Defining an interaction
In ContractCase, a contract is a series of interactions, defined by example. In English, an interaction might be "a getUser request looks like $X, and a success response looks like $Y":
Instead of making examples dependent on each other (and the corresponding explosion of complexity and potentially brittle tests), each interaction runs independently during contract definition.
This is achieved with state definitions, which you can define any preconditions that your interaction needs (for example "a user with ID 'foo' must exist").
A complete interaction looks like:
- States:
- When the server has a user named `` with ID
123
- When the server has a user named `` with ID
- a getUser request looks like $X, and
- the success response looks like $Y
For example:
- Typescript
- Java
import {
willSendHttpRequest,
inState,
stateVariable,
stringPrefix,
anyString,
HttpRequestConfig,
} from '@contract-case/contract-case-jest';
import { YourApi } from './YourApi.js';
it('behaves as expected', async () => {
await contract.runInteraction({
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',
},
},
}),
});
});
contract.runInteraction(
new InteractionDefinition<>(
List.of(
new InState("Server is up"),
new InState("A user with id \"foo\" exists")
),
new WillSendHttpRequest(HttpExample.builder()
.request(
new HttpRequest(HttpRequestExample.builder()
.method("GET")
.path("/users/foo")
.build())
)
.response(new HttpResponse(HttpResponseExample.builder()
.status(200)
.body(
Map.ofEntries(
Map.entry("userId", "foo"),
Map.entry("name", "john smith")
)
)
.build()))
.build())
),
);
After your contract is written, the provider verification will use state handlers to fill in the implementation details.
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:
- Typescript
- Java
await contract.runInteraction(
{
states: [
/* as above */
],
definition:
/* as above */
},
{ logLevel: 'debug' },
);
contract.runInteraction(
new InteractionDefinition<>(
List.of(
/* as above */
),
new WillSendHttpRequest(
HttpExample.builder()
/* as above */
.build())
),
IndividualSuccessTestConfigBuilder.builder()
.withLogLevel(LogLevel.DEBUG)
.build()
);
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 interaction might be failing.
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.