A customer service changes an identifier from a string to a number. Its own tests pass, and its API still returns a successful status. A billing application fails because it uses string operations on that field. The provider was tested, but the expectation held by a deployed consumer was never exercised.
A different release preserves the response exactly and still fails after deployment. The application cannot authenticate to its database because the deployed credentials are wrong. A contract test for the customer response would not necessarily reveal that problem either.
Contract testing and integration testing help answer different questions about such changes. Their boundaries can overlap: a provider verification test may exercise real application components, and an integration test may assert an API contract. The useful distinction is the evidence each test provides and the conditions under which that evidence remains relevant.
Start with the consumer’s actual dependency
An API description might list dozens of fields, while one consumer depends on three of them and a particular error response. Contract testing makes those expectations explicit. In a consumer-driven approach, the consumer’s tests interact with a controlled substitute for the provider and record the interactions that the provider must support.
Pact’s explanation of its workflow describes consumer tests producing contracts and provider verification checking those interactions against the provider. The important connection is between the contract and exercised consumer behaviour. A manually written fixture that no consumer code uses can look convincing while describing an imaginary integration.
Suppose billing loads a customer through GET /customers/C-104. It expects an identifier and a billing status:
{
"id": "C-104",
"billingStatus": "active"
}
The consumer test should invoke the code that makes and interprets this request. It can then check the behaviour billing needs, such as allowing invoice creation for an active customer. Copying the expected JSON into a test without exercising that code would leave its parsing and request construction unexamined.
For asynchronous interfaces, identify the equivalent observable agreement: the message a producer publishes and the fields a consumer interprets. A message contract can check that representation, while integration tests examine the selected broker configuration and delivery path. Ordering, duplicate delivery, and recovery need explicit scenarios where they matter. A valid message shape does not establish that a consumer will process a replay safely.
The provider side needs a controlled state in which customer C-104 exists and is active. Verification then checks what the provider actually returns for the interaction. Stable setup makes a failure interpretable; dependence on a shared account that another test can modify makes it harder to know whether the contract or the environment failed.
Decide what agreement needs to express
A useful contract describes observable dependencies precisely enough to catch breaking changes without freezing incidental details. If billing needs an identifier to be a string, a numeric value should fail verification. If it ignores an additional response field, requiring the response to contain exactly the original fields may create unnecessary coupling.
Matching rules deserve review alongside the consumer code. A matcher that accepts any string can miss a meaningful restriction if the consumer recognises only specific status values. Conversely, a fixed example identifier may be unnecessarily strict when any well-formed identifier is acceptable. The contract needs to represent the assumption the application makes.
Schema validation can contribute to this work, but it is not the whole agreement. A response can satisfy a schema and still represent the wrong customer or return success for an unauthorised request. JSON Schema and TypeScript types address structural and static concerns; the contract suite should include the observable interactions the consumer relies on.
That does not mean reproducing the provider’s entire business test suite in every consumer contract. Put detailed policy tests with the component that owns the policy. Include a cross-service case when it establishes an expectation needed by the consumer, such as the status and response structure returned for a missing customer.
Integration tests examine selected real connections
Integration testing exercises components working together. The scope can be small: an application repository using a real database engine, for example. It can also cover a service calling another deployed service. Naming the components and dependencies in scope is more informative than assuming every integration test means a complete production-like environment.
For billing, a database integration test might reveal an invalid migration, incorrect transaction handling, or a query that behaves differently from the in-memory substitute used in unit tests. A service integration test might exercise HTTP routing, serialization, and authentication configuration between billing and the customer service.
These checks are valuable precisely because a substitute cannot reproduce every property of the real dependency. They are also selective. A test using local credentials does not establish that production credentials are correct. A single request does not establish behaviour under contention. The environment and scenario determine the reach of the result.
| Question | Useful evidence | Important limit |
|---|---|---|
| Does billing send the expected request? | Consumer code exercised against its contract substitute | Substitute does not establish provider behaviour |
| Does this provider satisfy billing’s interactions? | Provider verification of the contract | Only represented interactions are checked |
| Does the query work with the selected database engine? | Database integration test | Deployment configuration may differ |
| Do deployed services authenticate and communicate? | Focused environment integration check | Other routes and load conditions remain separate |
| Are the intended consumer and provider versions compatible? | Verification results for those versions | Compatibility depends on the recorded evidence |
The table is a way to allocate evidence, rather than a mandatory sequence for every change. A small internal application may need fewer boundaries than a platform with independently deployed consumers.
Verify the version that will meet the consumer
A green contract result for the latest consumer does not necessarily cover an older version still running in production. Similarly, verifying a provider’s main branch says little about a different build selected for release. Record versions with the contracts and verification results so the deployment decision refers to the artefacts that will actually communicate.
The Pact Broker’s deployment compatibility checks use recorded contracts, verification results, and version relationships to assess whether a proposed deployment has the required compatibility evidence. The check is useful when the release process supplies accurate version and environment information. It cannot infer an unrecorded consumer or a dependency that was never represented.
For a breaking identifier change, a practical migration might preserve the existing field while introducing a new representation, update consumers, and remove the old field only when the supported consumers no longer depend on it. Contracts help establish which dependencies remain. The migration still needs an owner and an explicit support policy.
Make failures understandable enough to act on
When provider verification fails, the result should identify the consumer expectation, interaction, provider version, and actual difference. An identifier type change should be recognisable as such. A database unavailable during provider setup should not be reported as if it demonstrated an incompatible response.
Provider state setup needs the same discipline as other test data. Give scenarios clear ownership, avoid dependence on execution order, and isolate mutable records where necessary. Pact’s provider guidance is useful for understanding how verification connects recorded expectations to provider execution.
Integration failures need comparable context: which service instances were involved, which configuration applied, and where the request stopped. Correlation and trace identifiers can connect the test request to service records without making every assertion depend on a log message’s wording. Preserve enough evidence to distinguish a configuration failure from a behaviour change.
Avoid automatically rerunning every failure until the pipeline turns green. If a test depends on a shared environment, repeated execution may hide interference rather than resolve it. Examine the cause and improve the test’s isolation or the environment’s reliability. An unreliable check eventually stops influencing release decisions, even when it occasionally finds real defects.
Build the suite around credible failure modes
Review recent incidents and planned changes before deciding where to add coverage. If failures repeatedly involve consumers interpreting optional fields differently, strengthen the contract cases and matching rules. If they involve database constraints or network authentication, exercise those connections with focused integration checks.
For the billing example, a useful set might cover the active customer interaction, a missing customer response, a real database write, and one authenticated request through the deployed route. A separate test can exercise duplicate invoice prevention under competing requests. Each check has a specific consequence to examine and a reason to exist.
Keep a few broader workflow checks for outcomes that require the assembled system, but avoid using one slow environment to answer every question. Earlier, narrower feedback makes a response incompatibility easier to locate. Broader checks then examine the remaining risks created by deployment and composition.
The resulting release evidence should be explainable in ordinary terms: these consumers were represented, this provider build satisfied their interactions, these real dependencies were exercised, and these deployment conditions were checked. That explanation is more useful than a test count because it shows both what the team knows and where the next failure could still occur.





