Contract Files
In ContractCase, a contract is between two specific deployable things (eg, the user service, and the web frontend). It's written down in a contract file.
What's in a contract?
The contract file is written by ContractCase during contract definition. It describes all the interactions that the consumer expects the provider to implement.
Each of these interactions are described by example - meaning that they're a record of specific expectations that the consumer has:
- Expected Request
- For example: an HTTP
GET
for/users/123
- For example: an HTTP
- Example Response
- For example: a
200 OK
response containing aUser
object as JSON
- For example: a
To make it easy to reason about the examples in a contract, each example runs independently. This avoids complex and potentially brittle integration tests. To achieve this independence, any preconditions are handled by state setup functions. This means each interaction contains:
- Provider state(s)
- For example: "the server has a user named
John Smith
with ID123
"
- For example: "the server has a user named
- Expected Request
- For example: an HTTP
GET
for/users/123
- For example: an HTTP
- Example Response
- For example: a
200 OK
response containing aUser
object as JSON
- For example: a
The contract file contains at least one - but usually more - of these examples, plus some metadata about the contract. This metadata includes things like the consumer that generated it, the producer it is for, etc.
Here's a concrete example of one interaction:
- 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())
),
);
Do I need to open the contract file?
No, you shouldn't need to open the contract file. It's expected to be opaque to the user.
ContractCase should describe everything you need to know about it in the output of a test run. If you run into problems, you can set the log level to debug to find out what is happening during your test run, and this should provide everything you need.
Curious users are welcome to look in the file, of course. It's just JSON. If you do, take care not to modify it, or the expectations of the consumer that you verify won't be accurate.