A customer service changes a response field from customerId to id. Its own tests pass, and the new response looks reasonable to the team maintaining it. Checkout still reads customerId, however, so the next deployment breaks a workflow that neither team intended to change.
A contract test can catch that disagreement before both services are assembled in a shared environment. An integration test can expose it by running the connected components. The practical choice is where to detect this particular failure and what further evidence is needed before releasing the change.
Start With the Boundary Being Tested
A service contract describes the interaction between a consumer and a provider. For an HTTP API, that can include the method, path, headers, request body, status code, and response fields. A messaging contract can similarly describe the messages a producer emits and a consumer needs to understand.
Contract testing checks whether those expectations remain compatible. Integration testing exercises the interaction between components, such as an application and its database or two connected services. The terminology overlaps: some teams classify contract tests as a form of integration testing. For planning purposes, it helps to state which components are real, which are replaced, and what a passing result establishes.
In the checkout example, the consumer might call GET /customers/123 and rely on this response:
{
"customerId": 123,
"email": "sam@example.com"
}
The important requirement is that checkout can use the identifier and email it receives. Other customer fields may be irrelevant to that interaction. A test that requires the entire response to remain identical can make harmless additions look like breaking changes.
How Consumer-Driven Contracts Work
Pact's introduction to contract testing describes checking each application in isolation against a shared understanding of the messages exchanged. In its consumer-driven model, a consumer test calls a mock provider and exercises the consumer code that handles the response. The resulting contract records the interaction for verification against the provider implementation.
Both sides matter. A consumer test with a handwritten mock only establishes that the consumer works with the response the test supplied. Provider verification checks whether the actual provider can satisfy that expectation. If customerId disappears, verification should fail for a contract that requires it.
The contract should describe what the consumer uses, with suitable matching rules for values that vary. If checkout needs a numeric identifier, insisting on the literal value 123 in every response would usually assert the wrong property. If a particular status or value drives a decision, however, that expectation needs to be explicit.
Test Data Is Part of the Agreement
The request for customer 123 needs a known starting condition. The provider verification cannot reliably depend on whichever records happen to exist in a shared test database that morning.
Pact's provider states describe the preconditions for an interaction, such as a customer existing or a customer not being found. The provider's test setup establishes the relevant condition before verification. This keeps each interaction independent and makes a failure easier to interpret.
For checkout, separate interactions might cover a known customer and a missing customer. Those cases can establish the responses the consumer must handle. They do not, by themselves, prove that customer registration creates the right records or that every permission rule is correct. Pact's guidance on contract tests and functional tests makes this distinction: contract tests focus on communication, while the provider's functional tests need to establish its business behaviour.
What Connecting the Components Adds
Suppose both services satisfy the contract, but checkout's deployed credentials cannot access the customer API. Verification performed with test credentials will not reveal that configuration error. A test connecting the services through the relevant authentication path can.
Similarly, an application may produce the expected customer response when its repository is stubbed, yet fail against its database because a migration renamed a column. An integration test using the real database engine can exercise that boundary. It does not need the entire commerce platform running to provide useful evidence.
The scope matters more than the label. A local database test says little about production network rules. A staging request says little about a production-only identity configuration. Every test runs under particular conditions, and those conditions determine which failures it has a chance to detect.
| Question | Useful test scope | What remains outside that evidence |
|---|---|---|
| Can checkout handle the response it expects? | Consumer contract test exercising the API client | Whether the provider produces that response |
| Can the provider satisfy checkout's recorded expectations? | Provider contract verification | Unrecorded interactions and deployment configuration |
| Does the customer service read and write its database correctly? | Integration test with the database engine | The complete checkout journey |
| Can the deployed services communicate with the intended credentials? | Integration check through that environment's authentication path | Other environments and untested permissions |
| Can a customer complete an order? | End-to-end test of a critical journey | Every business rule, failure mode, and load condition |
Compatibility Depends on Versions
A passing verification result for the latest consumer and latest provider is useful only if those are the versions that will run together. During a rolling deployment, an older consumer may still be sending requests after the provider changes. Independent release schedules make this a routine concern.
The Pact Broker's can-i-deploy workflow uses contracts, verification results, and version information to check compatibility with a target environment. Its value depends on publishing the relevant results and recording deployments accurately. Missing evidence needs to remain visible rather than being treated as proof that an untested combination is safe.
For the field rename, the team could introduce id while retaining customerId, move consumers to the new field, and remove the old one only when the supported consumers no longer require it. Contract verification helps inform that sequence. The team still needs an explicit policy for older clients and consumers it cannot observe.
Keep Failure Behaviour in Scope
A successful customer lookup is only one part of checkout. The API can also time out, return an error, or become temporarily unavailable. Contract interactions can capture expected error responses and help check how the consumer interprets them. They do not establish the timing and resource behaviour of a running system under sustained failure.
If checkout uses a circuit breaker, focused tests should exercise opening, rejection, and recovery. Integration checks can then verify that the configured client, timeout handling, and dependency behave together as intended. A critical journey test can inspect what the customer sees when the lookup cannot complete.
This gives each test a reason to exist. It also makes failures easier to diagnose: a missing field, an incorrect database query, and an unhelpful outage screen should not all require the same large environment before anyone can investigate them.
Build the Suite Around Decisions
For this system, a practical starting point is consumer tests around the API client, provider verification for supported consumers, database integration tests, and a small set of complete checkout journeys. Add further coverage where a specific risk is still unexamined. Compare setup time, execution time, and diagnostic value before assuming that one category will always be cheaper than another.
The same care applies when AI helps produce the implementation and tests. A generated mock can repeat a generated client's mistaken assumption. Expectations still need to come from agreed behaviour, and someone needs to check that the verification actually challenges the implementation.
The useful outcome is a suite the team can explain. When a response field changes, compatibility tests should identify the affected consumer. When configuration prevents a connection, a test of that connection should expose it. When a customer cannot complete an order, a journey test should make the consequence visible. Together, those checks support a release decision whose evidence and limits are understood.