Contract-based testing is a method in which the two parties of an interface, provider and consumer, define a shared contract. The contract lists every interaction and serves as the mock for unit tests on both sides. That way, API changes show up early, and neither side needs the other system to be available at the same time.
Key Takeaways
- Contract-based testing protects consumers from breaking changes: if the provider changes an existing interface, the tests fail before the problem ever reaches integration.
- The Pact Broker keeps contracts current, because both sides test against the same central store rather than outdated documentation or static mocks.
- Contract tests check only the communication between consumer and provider, at unit test level, and not the business logic behind the interface.
- Consumer-driven contracts prevent unnecessary API work: the provider builds only what consumers actually ask for instead of offering every interface anyone might want.
What Is Contract-Based Testing?
Contract-based testing checks the interaction between two parties at an interface without both systems having to be up at test time. One party provides the interface, the other uses it. The contract is the agreement between them: it defines which interactions are possible, which requests go out and which responses come back.
At its core, a contract is an interface specification, just a living one. It documents the interface and stays current because provider and consumer agree on the defined interactions and store them under version control. The classic documentation that goes stale after the first iteration is no longer needed.
The two roles are clearly split. The provider is the backend that makes data available. The consumer is anything that needs that data: other products, internal or external services, or a frontend that talks to the backend through microservices. Both work against the same contract.
Consumer-Driven Contract Testing: Build Only What’s Needed
In the consumer-driven approach, the consumer defines the data and interactions it needs, and the provider implements exactly that. This stops a provider from exposing interfaces for every piece of data imaginable that nobody ends up calling.
Behind it is the YAGNI principle, “you aren’t gonna need it.” Instead of building every possible interface just in case, only what a specific consumer has asked for gets built. The result is an API shaped by real demand.
In practice it works like this: a team publishes its contracts on a Pact Broker. Other teams ask for an interface for certain data and are pointed to the matching pact. They implement against it, with no joint alignment ritual needed up front.
The approach also shows how interfaces are really used. If no one pulls a contract, the provider team knows it has an unused interface and can question whether it’s needed.
How Testing with Pact Works
Pact is one of the widely used frameworks for contract-based testing and is known for being lightweight and easy to set up. The tests run at unit test level: what gets tested are the classes and methods that call an API, not the API itself.
The interaction is mocked using the contract, so no real API call happens in the test. That decouples the systems. You don’t have to wait until the other side has finished its implementation or until a system happens to be reachable. You stay in your own territory and can test as often as you like.
This even works for an interface that doesn’t exist yet. Provider and consumer agree on the interactions, and both use the contract as their mock. Both develop against the same version and meet in the middle, with no finished implementation required on the other side.
A key safeguard is catching breaking changes. If the provider changes the interface, the contract makes sure existing interactions keep working. New interactions that no consumer uses yet don’t affect anyone. Only changes to APIs already in use make the tests fail.
The Pact Broker Keeps Contracts Current
The Pact Broker is where contracts are stored and versioned. Providers and consumers publish their pacts there and pull them from there, so everyone tests against the same current version.
During test runs, pacts can be loaded straight from the broker. That means you test against the current implementation, not an old copy. This is exactly why the broker is the recommended choice over homemade storage on your own servers or in a repository.
The tests belong in the CI pipeline, where you see right away whether an interface change causes trouble. If the interface changes, the contract tests fail and the team finds out early. Pact also fits into container environments; there’s a Docker image for it.
Resist the Urge to Test Too Much
The most common pitfall is trying to test too much. Contract-based testing checks the communication, not the business logic behind the interface. Anyone who tries to cover the provider’s functional tests with Pact is misusing the tool.
The question is simply whether an interaction works. When a new item is created in an online shop, you check whether the interface reports success or returns an error, for example because a required field is missing. Why a validation fails inside the provider belongs one level up and is none of the contract test’s business. What matters is that the API returns the right error.
Hold back on the data, too. Contract tests check data types, not concrete values. The test expects a string, not one particular string. That keeps tests flexible and avoids flaky tests that break every time the data changes.
This restriction takes some rethinking. With interfaces, it’s tempting to test the other system while you’re at it. That’s exactly what drops out here. The test stays focused on the interaction, and that’s all it’s meant to do.
When Contract-Based Testing Is the Wrong Tool
Contract-based testing needs two parties willing to sign up to a contract. If one side is missing or won’t play along, no useful contract comes about. Several scenarios argue against the approach:
- Public APIs: The consumer side that drives development is missing. The provider exists, but consumers don’t shape the interface. Not a good fit for contract testing.
- Performance and load testing: Pact isn’t built for this. Even though API calls are simulated, these tests belong in other tools.
- No control over the data: If the data provided can change independently in the background, the interactions no longer reflect reality.
- The other side isn’t willing: If the other party doesn’t want contract-based testing for organizational or political reasons, you won’t meet in the middle.
“We wanted to design the interface through contracts, and the others said, no, we’re not using that. Even though it has so many advantages.”
(Mariusz Smoliński)
That last hurdle is the most common. A contract is an agreement, and without the other side’s signature there’s no deal. The benefits are obvious, but that alone doesn’t win over every team.
How Many Contract Tests Does an Interface Need?
The number of tests usually stays manageable. How many you end up with depends on how complex the cases are that an interface has to handle, and on what matters to the team.
As a rule of thumb, covering the happy path and the error cases is enough. You rarely need more. The goal is the smallest set of tests that secures the interaction, precisely because it’s so tempting to test too much.
Keeping it lean is part of the concept, not a compromise. Contract tests don’t replace end-to-end tests or functional provider tests. They complement them by adding a level where the question of who is responsible never comes up. Both sides test against the same contract, no matter what the other system is doing at the moment.
Frequently Asked Questions
Why does traditional interface documentation become outdated, but a contract does not?
At its core, a contract is an interface specification, only more dynamic: the provider and consumer agree on the possible interactions, version them, and both perform testing against them. This ensures that the description remains tied to the tests. Traditional documentation, which becomes outdated after the first iteration, is no longer needed because the contract specifies which requests are sent and which responses are expected.
What does the YAGNI principle have to do with API design?
In the consumer-driven approach, the consumer defines which data and interactions it needs, and the provider implements exactly that. This prevents the creation of an interface for every conceivable piece of data that ultimately no one will ever use. A side effect: If no one uses a specific contract, the provider team recognizes an unused interface and can question its necessity.
Can you test an interface that the other side hasn’t implemented yet?
Yes. The provider and consumer agree on the interactions, and both use the contract as a mock. The actual API call does not take place during the test, so no system needs to be accessible. Both sides develop based on the same version and meet halfway, without waiting for a finished implementation from the other side.
How can a team detect early on that an API change will break existing users?
Contract tests in the CI pipeline trigger as soon as an interaction that is already in use is no longer satisfied. New interactions that no consumer is using yet won’t affect anyone. The error is therefore detected not during integration, but during the build, specifically at the unit test level for the classes and methods that call the API.
Is it sufficient to store contracts in your own repository or on your own server?
A better option is a centralized, version-controlled repository such as the Pact Broker. Providers and consumers upload their pacts there and retrieve them from there, ensuring that everyone performs testing against the same current version. If the test loads the pact directly from the broker, it checks against the current implementation rather than an old copy. Homemade storage solutions carry precisely this risk.
Do contract tests replace end-to-end testing or functional testing of the provider?
No. Contract tests exclusively verify the communication between the consumer and the provider, not the business logic behind the interface. Anyone who uses a contract framework to implement functional provider tests is misusing the tool. The tests complement the other levels and introduce a layer where the question of responsibility doesn’t even arise.
Should a contract test expect specific data values?
No; data types are what are tested. A string is expected, not a specific desired string. This keeps the tests flexible and prevents flaky tests that fail every time the data changes. Along the same lines is the restriction to determining whether an interaction works: whether the interface reports success or returns an appropriate error, such as when a required field is missing.
Is contract-based testing suitable for public APIs?
No. With public APIs, the consumer side (which drives development) is missing: the provider exists, but the interface is not shaped by the consumer. This approach is also unsuitable for performance and load testing, as well as in cases where the provided data can change independently in the background, because the interactions then no longer reflect the actual state.


