Microservices allow teams to develop, test, and deploy different parts of an application independently. However, independent deployments introduce a problem: one service can change its API without the team responsible for another service knowing about it.
Consider an order service that calls a payment service. The order service expects the payment service to return a payment ID and a status. If the payment service renames a response property or changes its response structure, the order service may stop working even though both services build successfully.
Traditional unit tests may not catch this problem because they usually test one service at a time. Integration tests can help, but testing every service combination against running dependencies can become expensive as the system grows.
Pact contract testing offers another approach. It verifies that a service provides the interactions its consumers expect, helping teams identify incompatible changes before deployment.
This article explains how Pact works, how to implement consumer and provider tests, and how to use contract verification in a microservices development workflow.
What Is Pact Contract Testing?
Pact is a contract testing framework designed to verify communication between applications and services.
Pact uses a consumer-driven approach. The consumer defines the interactions it requires from a provider. These interactions are recorded in a contract, which the provider verifies against its implementation.
For example, an order service might require the payment service to support this interaction:
- Accept a request containing an order ID and payment amount.
- Return HTTP 200 for a successful payment.
- Include a payment ID and status in the response.
The contract describes the expected request and response. The consumer test checks that the consumer can work with the defined interaction, while the provider test checks that the actual provider satisfies it.
The important point is that Pact verifies the agreed interface between services without requiring the entire production environment to run for every contract test.
Understand the Consumer and Provider
Before implementing Pact, identify the roles of the services.
Suppose an e-commerce application contains these services:
Order Service
|
| HTTP request
v
Payment Service
|
| Payment response
v
Order Service
The order service is the consumer because it sends requests to the payment service. The payment service is the provider because it handles those requests.
| Component | Responsibility | Testing goal |
|---|
| Consumer | Calls another service | Verify that the application sends the expected request and handles the expected response |
| Provider | Exposes the API | Verify that its implementation satisfies the consumer's contract |
| Pact contract | Records the interaction | Describe the expected request and response |
| Contract broker, when used | Shares contracts and verification results | Help teams coordinate compatible releases |
A single service can be both a consumer and a provider. For example, an order service might consume a payment API while providing an order API to a shipping service.
Set Up Pact Contract Testing in .NET
For a .NET application, you can use the Pact .NET ecosystem to write consumer and provider contract tests.
The exact package names, APIs, and configuration options depend on the Pact .NET version you choose. Keep the consumer-testing and provider-verification packages compatible with that version, and follow the matching documentation when configuring your test project.
For this example, assume the payment service exposes a POST /api/payments endpoint.
The consumer sends a request containing an order ID and an amount:
public sealed record CreatePaymentRequest(
int OrderId,
decimal Amount);
The provider returns a payment response:
public sealed record PaymentResponse(
string PaymentId,
string Status);
The expected JSON request might look like this:
{
"orderId": 101,
"amount": 250.00
}
The response might look like this:
{
"paymentId": "pay-101",
"status": "Completed"
}
These examples define the intended API contract. The next step is to verify that the consumer and provider agree on it.
Write a Consumer Contract Test
A consumer contract test describes the interaction the order service expects from the payment service.
A typical test defines:
- The expected provider state.
- The HTTP method and path.
- The request headers and body.
- The expected response status.
- The response headers and body.
The following example illustrates the interaction using Pact-style pseudocode. It shows the structure of a consumer contract test without assuming a specific version of the Pact .NET API.
// Illustrative Pact-style pseudocode.
// Adapt the builder methods to your installed Pact .NET version.
var interaction = new
{
Description = "a request to create a payment",
ProviderState = "the order is ready for payment",
Request = new
{
Method = "POST",
Path = "/api/payments",
Headers = new
{
ContentType = "application/json"
},
Body = new
{
orderId = 101,
amount = 250.00m
}
},
Response = new
{
Status = 200,
Headers = new
{
ContentType = "application/json"
},
Body = new
{
paymentId = "pay-101",
status = "Completed"
}
}
};
In a real test, the selected Pact .NET consumer API turns the interaction into a contract and provides a mock HTTP server. Your consumer application sends its request to that mock server, which returns the configured response.
The test then verifies that the consumer can process the response correctly.
For example, the order service should not assume that the response contains only a status code. It should parse the payment ID and handle the payment status according to its business rules.
Why Use a Mock Provider?
A mock provider allows the consumer test to run without starting the real payment service.
This has several benefits:
- Consumer tests run without depending on payment-service availability.
- Test data is predictable.
- Failures can be reproduced locally.
- Consumer behavior can be checked before the provider is deployed.
However, a consumer test alone does not prove that the real payment service satisfies the contract. Provider verification is required for that.
Verify the Contract Against the Provider
Provider verification checks whether the actual payment service can satisfy the interactions defined by its consumers.
During verification, Pact sends requests to the provider and compares the resulting responses with the contract.
For example, the contract expects the payment service to return:
{
"paymentId": "pay-101",
"status": "Completed"
}
Suppose a developer changes the response model:
public sealed record PaymentResponse(
string TransactionId,
string Status);
If the API now returns transactionId instead of paymentId, provider verification can detect the mismatch.
The provider might still compile and pass its unit tests. However, the contract test fails because the implementation no longer satisfies the consumer's expectation.
Configure Provider States
A provider state describes the conditions required before an interaction can be verified.
For example:
- An order is ready for payment.
- A payment record exists.
- A requested product is available.
Provider states help create predictable test conditions. They should be implemented in a test environment, where the verification process can arrange the required data without affecting production records.
For the payment example, the provider-state setup might create an order with ID 101 and configure the test payment system to return a successful result.
Avoid relying on random production data or external payment gateways during provider verification. Use controlled test dependencies so that a contract failure indicates an actual compatibility problem rather than an unrelated service outage.
Consumer Tests vs. Provider Verification
Both parts of Pact testing serve different purposes.
| Area | Consumer contract test | Provider verification |
|---|
| Main question | Can the consumer use the expected interaction? | Does the provider implement the agreed interaction? |
| Execution target | Consumer application with a mock provider | Actual provider implementation in a test environment |
| Main failure | Consumer sends an unexpected request or cannot handle the response | Provider returns a response that violates the contract |
| Dependency on the other service | Does not require the real provider | Requires the provider to be available in the verification environment |
| Best time to run | During consumer development and CI | During provider CI and release validation |
Running both tests provides stronger protection than relying on either one alone.
Handle Contract Changes Safely
Not every API change is breaking. Adding an optional response field may be compatible with existing consumers, while removing a field that a consumer requires may cause a failure.
Consider a payment response that originally contains:
{
"paymentId": "pay-101",
"status": "Completed"
}
A provider adds an optional timestamp:
{
"paymentId": "pay-101",
"status": "Completed",
"completedAt": "2026-10-27T09:00:00Z"
}
This addition is often backward-compatible for consumers that ignore unknown properties. However, compatibility depends on how the consumer parses the response and how the contract is defined.
A safer approach is to evaluate changes against the actual consumers and their expectations.
Follow a Compatibility-First Release Process
When changing an API used by multiple services:
- Identify the consumers that depend on the affected endpoint.
- Review their current contracts.
- Make the provider change without breaking existing expectations whenever possible.
- Run provider verification against the relevant consumer contracts.
- Update consumer contracts when a deliberate interface change is required.
- Coordinate deployment if consumers and providers cannot be upgraded independently.
For breaking changes, consider introducing a new endpoint or API version rather than changing the existing contract without warning.
Integrate Pact into CI/CD
Contract tests are most effective when they run automatically as part of development and release workflows.
A typical pipeline includes:
Consumer Change
|
v
Consumer Contract Tests
|
v
Publish Contract
|
v
Provider Contract Verification
|
v
Record Verification Results
|
v
Release Compatibility Check
|
v
Deploy
A contract broker can store contracts and verification results so that teams can discover which consumer and provider versions have been verified together.
For example, the order-service team can publish a new contract after changing its payment request. The payment-service team can then verify the contract against its implementation before releasing a new version.
In a larger system, release checks should consider the specific application versions and contracts involved. A successful verification against one consumer contract does not automatically prove compatibility with every consumer.
Do not treat the existence of a contract as proof that deployment is safe. The release process must use current contracts and verification results, and it must account for the versions being deployed.
Common Pact Testing Mistakes
Testing Only the Consumer
A consumer test can pass even when the real provider does not implement the expected response. Add provider verification to complete the contract-testing process.
Writing Contracts for Every Possible Response
Contracts should represent meaningful consumer requirements, not every implementation detail. Overly strict contracts can prevent harmless changes and make maintenance expensive.
Depending on Production Data
Provider verification should use controlled test data. Production dependencies can make tests unreliable and introduce unnecessary risk.
Ignoring Provider States
If the required data does not exist during verification, the provider may return a legitimate 404 instead of the expected response. Configure provider states to prepare the correct conditions.
Updating Contracts Without Reviewing Consumers
A contract change can affect multiple applications. Review the consumer requirements before accepting a change that removes or modifies an existing field.
Treating Contract Tests as End-to-End Tests
Pact verifies interactions between a consumer and a provider. It does not replace unit tests, integration tests, security tests, or end-to-end tests that validate complete business workflows.
Best Practices for Production Microservices
Use the following practices to keep contract testing useful as your system grows.
- Define contracts from real consumer needs. Include the request and response details that consumers actually rely on.
- Run consumer tests early. Catch incorrect requests and response handling before changes reach the provider team.
- Verify contracts in provider CI. Make compatibility failures visible before release.
- Keep provider states isolated. Prepare test data without relying on production systems.
- Review breaking changes explicitly. Decide whether a change is backward-compatible before updating the contract.
- Track verification results. Use a shared contract workflow when multiple teams release services independently.
- Keep other tests in place. Contract tests complement unit and integration tests rather than replacing them.
- Test authentication where relevant. Include required headers and security behavior when they form part of the consumer-provider interaction.
Advantages and Disadvantages of Pact
| Advantages | Disadvantages |
|---|
| Detects incompatible service interactions before release | Requires contracts and verification tests to be maintained |
| Reduces dependence on full end-to-end environments | Does not verify every business workflow |
| Helps teams deploy services independently | Provider states require deliberate test setup |
| Makes API expectations visible | Poorly designed contracts can become overly restrictive |
| Supports coordinated releases across teams | Broker and CI/CD integration add operational work |
Pact is particularly useful when services are developed by different teams and deployed independently. For a small application with only a few tightly coupled components, ordinary integration tests may be enough. As the number of consumers and providers grows, contract testing can provide clearer ownership of API compatibility.
Summary
Pact contract testing helps microservices teams verify that consumers and providers agree on how they communicate. Consumer tests describe the interactions an application needs, while provider verification checks those interactions against the actual service implementation.
In a .NET environment, Pact can complement ASP.NET Core integration tests, unit tests, and OpenAPI validation. The key is to maintain meaningful contracts, use predictable provider states, and run verification in CI/CD before releasing changes.
When multiple teams own independently deployed services, this approach makes API expectations explicit and helps catch compatibility problems before they affect users.
Join the conversation! Your thoughts help the community grow.