Consumer-driven contract testing in .NET: catch API breaks before deploy
In a microservice architecture a provider can pass all of its own tests and still break another service by reshaping its JSON. This guide walks through consumer-driven contract testing on .NET 10 with PactNet 5.0.1: generating contracts from real consumer clients, verifying them against the provider, publishing to a Pact Broker, and gating deployments in CI.
Key points
- Contracts are generated with PactNet 5.0.1 on .NET 10 LTS, targeting Pact specification v4
- Provider verification exercises the consumer's real `HttpClient` and deserialization settings rather than handcrafted requests
- Provider states declare preconditions such as "product 101 exists"; every interaction must pass independently
- Pact and OpenAPI are complementary: `oasdiff` catches removed endpoints no Pact covers, Pact catches runtime serialization drift
- PactNet ships native components, so verify on a glibc-based Ubuntu x64 runner, not an Alpine container
- A local Pact Broker and PostgreSQL run via Docker Compose; pin image digests and replace local credentials for CI
Independent deployments mean production runs a mixture of releases, not everyone's latest commit, so a provider can pass its own unit and integration tests while breaking a caller. The article's example is a Product API that renames `price` to `unitPrice`: the Order service either throws or, worse, receives a default zero and builds a plausible but wrong order line.
The fix is to make each consumer's expectation executable. Order and Shipping services generate contracts with PactNet, publish them to a Pact Broker, and the provider is verified against those interactions, with provider states describing preconditions. The guide also covers matching rules, OpenAPI comparison with `oasdiff`, CI gating, and practical caveats such as PactNet's native dependencies requiring a glibc-based runner rather than Alpine.
Real-world use
- Verifying the price and currency agreement between an order service and a product catalog API before the provider team ships
- A public ASP.NET Core API with several consumers where a breaking change should fail only the affected consumer relationship, not the whole surface
- Keeping a shipping service that needs only two fields from freezing the rest of the response shape in its contract
- Gating deployments in CI by checking Pact Broker verification results to decide which service versions may reach an environment
Why it matters
Compatibility evidence moves from a shared staging guess to fast, repeatable tests that run before merge, so teams keep deploying independently without coordinated releases.