You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Optional values in API responses are a common challenge when using Pact because there is no optional matcher. You cannot define something like stringOrNull(...) to represent lastLogin in this schema:
Well, actually 🤓 ....the core engine does support the predicates AND/OR, but we discourage (any in many cases - don't support) OR because it effectively supports optional elements.
e.g. given this setup:
DslPart body = new PactDslJsonBody()
.numberValue("valueA", 100)
.and("valueB","AB", PM.includesStr("A"), PM.includesStr("B")) // Must match both matching rules
.or("valueC", null, PM.date(), PM.nullValue()) // will match either a valid date or a null value
UsingOR could lead to situations where not all variations are covered in the Pact tests. When the consumer side test is run, only one value will be tested to work with the client for the optional properties (e.g. for valueC it will be the current date, not null). If the real provider actually populates the null we don't know if the consumer can handle it.
However, it's a common complaint and can occasionally stir up some, ahem, robust discussions.
Discussion
What if we could support this situation? In order to gain the confidence, we need to ensure that the consumer test has had all combinations covered and all of the optional fields have been covered in the provider response (maybe?).
Here we discuss some options that we might consider to support it safely.
Proposal 1: Support the optionals as a matcher (unsafe)
This effectively supports the OR matcher, without changing the validation behaviour.
Pros: It makes some people happy
Cons: It suffers from the issues described above
Proposal 2: Automatically test combinations on the consumer side (safe)
The idea expressed here (and discussed previously, possibly in our slack community) would be to automatically "expand" optional fields into multiple tests, thereby giving us confidence the consumer can handle both scenarios. For example:
Example
await pact
.addInteraction()
.uponReceiving('a request to list all users')
.withRequest('GET', '/users')
.willRespondWith(200, (builder) => {
builder.jsonBody(
Matchers.eachLike({
id: '123',
lastLogin: Matchers.Or(Matchers.string('2025-10-20'), null),
})
);
})
.executeTest(async (mockserver) => {
// this block executes _once_ for each variation of the optional fields. In this case, it would execute twice
const client = new UsersClient(mockserver.url);
const users = await client.getUsers();
expect(users[1].lastLogin).to.be.oneOf([null, '2025-10-20']);
});
This would ensure the client can handle each of the scenarios. In the contract this could be captured in a single interaction, with the matching rules incorporating the OR predicate to apply. On the provider side, so long as it responds within the expected range, that would be considered successful.
So far, so good. It gets more complicated, though. Consider a more deeply nested object:
{id: "123",lastLogin: Matchers.Or(Matchers.string("2025-10-20"),null),address: {street: "123 Main St",city: "Somewhere",zip: "12345",residenceType: Matchers.Or("HOUSE","CARAVAN","NONE"),// If residenceType is HOUSE:residenceDetails: Matchers.OR({numberOfBedrooms: 3,hasGarage: true,// ...},// If residenceType is CARAVAN:{tareWeight: 1200,licenseNumber: 'XYZ 1234',// ...},// If residenceType is NONE:undefined),},};
Now the combinations get more complicated to calculate - the framework could not reliably infer (and therefore create tests for) the case for different residenceTypes (e.g. HOUSE) and the appropriate subsequent payload structure for residenceDetails.
Even JSON schema cannot describe this constraint. Put another way:
The structure of residenceDetails is different depending on the value of residenceType
It's only valid for residenceDetails to be omitted entirely if residenceType is NONE
It's only valid for residenceDetails to be a "Caravan" structure if residenceType is CARAVAN
etc.
This highlights the problem - if the framework created all combinations, it would create ones that are likely invalid.
Disambiguating
One strategy to break this complexity, would be to only allow the OR matcher on a primitive JSON value within an object (i.e. not an object or an array)
{id: "123",lastLogin: Matchers.Or(Matchers.string("2025-10-20"),null),address: {street: "123 Main St",city: "Somewhere",zip: "12345",residenceType: "HOUSE"residenceDetails: {numberOfBedrooms: 3,hasGarage: true,additionalDetails: Matchers.OR("description",null)// ... other fields that may be optional},},};
Now it's clearer what the combinations are: a set of tests where all of the left side of the clauses are used, and a set of tests where all of the right side of the clauses are null (or undefined etc.).
It is still possible that there is a dependency on one combination that affects the possible later combinations, but this could then likely be corrected (by the user) by created distinct cases that make the intent clearer.
How about the provider side?
This becomes a little less clear. Ideally, all optionals have had exhaustive checks on the provider side (i.e. all variants covered), however the simplest approach would be to continue as long as each response matches the schema given by the consumer.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Optional values in API responses are a common challenge when using Pact because there is no optional matcher. You cannot define something like
stringOrNull(...)to representlastLoginin this schema:Well, actually 🤓 ....the core engine does support the predicates
AND/OR, but we discourage (any in many cases - don't support)ORbecause it effectively supports optional elements.e.g. given this setup:
Using
ORcould lead to situations where not all variations are covered in the Pact tests. When the consumer side test is run, only one value will be tested to work with the client for the optional properties (e.g. forvalueCit will be the current date, notnull). If the real provider actually populates thenullwe don't know if the consumer can handle it.However, it's a common complaint and can occasionally stir up some, ahem, robust discussions.
Discussion
What if we could support this situation? In order to gain the confidence, we need to ensure that the consumer test has had all combinations covered and all of the optional fields have been covered in the provider response (maybe?).
Here we discuss some options that we might consider to support it safely.
Proposal 1: Support the optionals as a matcher (unsafe)
This effectively supports the
ORmatcher, without changing the validation behaviour.Pros: It makes some people happy
Cons: It suffers from the issues described above
Proposal 2: Automatically test combinations on the consumer side (safe)
The idea expressed here (and discussed previously, possibly in our slack community) would be to automatically "expand" optional fields into multiple tests, thereby giving us confidence the consumer can handle both scenarios. For example:
Example
This would ensure the client can handle each of the scenarios. In the contract this could be captured in a single interaction, with the matching rules incorporating the
ORpredicate to apply. On the provider side, so long as it responds within the expected range, that would be considered successful.So far, so good. It gets more complicated, though. Consider a more deeply nested object:
Now the combinations get more complicated to calculate - the framework could not reliably infer (and therefore create tests for) the case for different
residenceTypes (e.g.HOUSE) and the appropriate subsequent payload structure forresidenceDetails.Even JSON schema cannot describe this constraint. Put another way:
residenceDetailsis different depending on the value ofresidenceTyperesidenceDetailsto be omitted entirely ifresidenceTypeisNONEresidenceDetailsto be a "Caravan" structure ifresidenceTypeisCARAVANThis highlights the problem - if the framework created all combinations, it would create ones that are likely invalid.
Disambiguating
One strategy to break this complexity, would be to only allow the
ORmatcher on a primitive JSON value within an object (i.e. not anobjector an array)Now it's clearer what the combinations are: a set of tests where all of the left side of the clauses are used, and a set of tests where all of the right side of the clauses are
null(orundefinedetc.).It is still possible that there is a dependency on one combination that affects the possible later combinations, but this could then likely be corrected (by the user) by created distinct cases that make the intent clearer.
How about the provider side?
This becomes a little less clear. Ideally, all optionals have had exhaustive checks on the provider side (i.e. all variants covered), however the simplest approach would be to continue as long as each response matches the schema given by the consumer.
All reactions