Contract Testing vs Integration Testing: What Each Can Tell You

How to check service compatibility early while keeping evidence that the connected system works.

  • Software Testing
  • APIs
  • Distributed Systems
Contract Testing vs Integration Testing: What Each Can Tell You

Key takeaways

  • Consumer-driven contract tests connect a consumer's expectations with verification against the provider implementation.
  • Integration tests exercise real component boundaries, with confidence limited by the dependencies and environment included.
  • Contracts should describe interactions the consumer relies on without freezing incidental provider details.
  • Deployment checks need verification results for the versions that will run together, alongside tests of critical workflows.

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.

QuestionUseful test scopeWhat remains outside that evidence
Can checkout handle the response it expects?Consumer contract test exercising the API clientWhether the provider produces that response
Can the provider satisfy checkout's recorded expectations?Provider contract verificationUnrecorded interactions and deployment configuration
Does the customer service read and write its database correctly?Integration test with the database engineThe complete checkout journey
Can the deployed services communicate with the intended credentials?Integration check through that environment's authentication pathOther environments and untested permissions
Can a customer complete an order?End-to-end test of a critical journeyEvery 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.

A useful test makes a specific failure easier to find and leaves its limits clear to the people relying on it.
Julia Norton

© 2026 Julia Norton.