Triggers and response tests
If ContractCase needs to invoke your code during a test, then you will need to define a trigger
, and either a testResponse
or testErrorResponse
function.
You will need a trigger function if your test requires a function or method call to kick off your consumer test (eg, if you are writing a contract for an HTTP client). If you're writing a contract for a consumer that is externally called (for example, if the code under test is an HTTP server), then you will need triggers at verification time. For more details, check the ContractCase Example Types documentation.
Definitions
type TriggerFunction = <TriggerReturnType>(config: MockConfig): TriggerReturnType
The trigger function is called by case. You should call your real client code
here, and return the response that your client returns. The MockConfig
object
contains the details of the mock that ContractCase has set up. TriggerReturnType
is an
implicit type parameter, and is only used to type testResponse
testResponse =
(response: TriggerReturnType, config: MockConfig)
=> Promise<unknown> | void
In this function you test the response object that your trigger returns. It is separate from the trigger because it is called at a different part of the ContractCase Example Lifecycle, and to help ensure that you have confirmed that your code can understand the response.
testErrorResponse: (error: Error, config: MockConfig): Promise<unknown> | void
This is equivalent to the testResponse
function, but for trigger
calls that are expected to fail with an error.
Providing triggers during contract definition
There are three ways to provide triggers during contract definition:
- If your trigger is expected to return successfully, use
trigger
andtestResponse
in arunExample
call - If your trigger is expected to throw an error (or reject a promise), use
trigger
andtestErrorResponse
in arunRejectingExample
call - In a
triggers
object passed in the ContractCaseConfig, describing multiple triggers and test functions (see the section on contract verification below).
With runExample
If we define this example at the client side, we will also need a trigger, and a way to test the response object:
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',
},
},
}),
},
{
// The trigger is the invocation of your client code
// It receives some configuration with details of how to contact the mock
// This function should return the business object that your API returns
trigger: (config: HttpRequestConfig) =>
new YourApi(config.baseUrl).getUser('foo'),
// The testResponse function is used to check the business object.
// This is an important step, as without it, ContractCase can't be sure that your
// calling code can understand the objects that your code is generating.
//
// It receives the object that was returned by your trigger
// If your trigger throws an error, the test will fail
testResponse: (user) => {
expect(user).toEqual({ userId: 'foo', name: 'john smith' });
}
})
With runRejectingExample
If your API is expected to throw an error during the invocation, then you should use runRejectingExample
instead of runExample
, and use testErrorResponse
instead of testResponse
:
await contract.runRejectingExample(
{
states: [inState('Server is up'), inState('No users exist')],
definition: willSendHttpRequest({
request: {
method: 'GET',
path: '/users/foo',
},
response: {
status: 404,
},
}),
},
{
// The trigger is the invocation of your client code
// It receives some configuration with details of how to contact the mock
// This function should return the business object that your API returns
trigger: (config: HttpRequestConfig) =>
new YourApi(config.baseUrl).getUser('foo'),
// The testErrorResponse function is used to check the business object.
// This is an important step, as without it, ContractCase can't be sure that your
// calling code can understand the objects that your code is generating.
//
// It receives the object that was thrown by your trigger.
// If your trigger does not throw an error, the test will fail
testErrorResponse: (e) => {
expect(e).toBeInstanceOf(UserNotFoundError);
},
},
);
Triggers and testResponse
/testErrorResponse
functions are not written to the contract.
Providing triggers in contract verification
In CaseConfig
In both contract definition and contract verification, you can provide a TriggerGroupMap
object to your CaseConfig
, under the key triggers
.
Each TriggerGroup
is an object keyed by request name, where each value is of type:
{
trigger: TriggerFunction;
testResponses: Record<string, TestResponseFunction>;
testErrorResponses: Record<string, TestErrorResponseFunction>;
}
If you provide this triggers
object, ContractCase will first find the request trigger,
then find the matching testResponse
or testErrorResponse
function associated
with that trigger. The test functions are associated with the trigger because
sometimes the same response needs to be tested differently when it is received by
different triggers.
{
triggers: new TriggerGroupMap()
.addTriggerGroup('an http "GET" request to "/health" without a body', {
trigger: (config: HttpRequestConfig) => api(config.baseUrl).health(),
testResponses: {
'a (200) response with body an object shaped like {status: "up"}': (
health,
) => {
expect(health).toEqual('up');
},
'a (200) response with body an object shaped like {status: <any string>}':
(health) => expect(typeof health).toBe('string'),
},
testErrorResponses: {
'a (httpStatus 4XX | 5XX) response without a body': (e) => {
expect(e).toBeInstanceOf(ApiError);
},
'a (503) response with body an object shaped like {status: "down"}': (
e,
) => {
expect(e).toBeInstanceOf(ApiError);
},
},
})
.addTriggerGroup(
'an http "GET" request to "/health" with the following headers {accept: "application/json"} without a body',
{
trigger: (config: HttpRequestConfig) => api(config.baseUrl).health(),
testResponses: {
'a (200) response with body an object shaped like {status: "up"}': (
health,
) => {
expect(health).toEqual('up');
},
},
},
);
}