pact-net: consumer-driven contract testing for .NET services
.NET version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
At a glance
- What is it?
- pact-net is the .NET implementation of Pact, where a consumer test writes down its assumptions about an API, a mock server replays them, and the provider later replays the same contract to prove it has not broken. Contracts are shared through Pact Broker or PactFlow for CI pipelines.
- Who is it for?
- pact-net suits a .NET team whose services are consumed by other teams, where a shared contract file and a mock server let frontend and backend work proceed without a joint integration environment. It does not suit a single service with one internal caller, because the broker, the pact lifecycle and the provider verification step cost more to set up than the shared integration test they replace, and the matching rules still require both sides to agree on a contract vocabulary.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The consumer writes the contract, the provider has to honour it
pact-net is the .NET version of Pact, and the repository description puts the split cleanly: the consumer project gets a mock service and a DSL, the service provider project gets interaction playback and verification. The project site is pact.io.
The direction of authorship is the whole idea. A consumer-driven contract test means the consumer writes a test stating its assumptions and needs about the provider's API. Running that test produces a contract, a pact file, which is shared with the provider. The provider then replays that contract against the real implementation and fails if the behaviour has drifted.
The stated benefit is replacing expensive and brittle end-to-end integration tests with fast unit tests, and the feature list claims support for HTTP and REST as well as event-driven systems, a configurable mock server, matching rules that stop tests becoming brittle, and integration with Pact Broker or PactFlow for CI and CD workflows. Pact itself is described as supporting 12 or more languages, which is what makes the pact file a shared artefact rather than a .NET specific one.
The consumer side also gets side effects for free. The README lists understanding your system usage, documenting your APIs, removing the need for complex data fixtures, and reducing reliance on complex test environments among the reasons to use Pact. Those follow from the pact file being a machine readable description of real traffic.
The samples/OrdersApi folder is where the fuller picture sits, since it uses both HTTP and message interactions along with provider states and matchers.
Pact.V4 and the Rust backend behind the mock server
The consumer test in the documentation is written against the V4 API, which is the current generation of the Pact specification in this library. A consumer fixture starts by naming both sides and pointing at a pact directory.
var pact = Pact.V4("Something API Consumer", "Something API", new PactConfig());The default directories are the current directory's ..\..\pacts and ..\..\logs, and a PactConfig can override PactDir to point somewhere else, which matters when the test project sits at a different depth in your solution than the sample assumes.
From there the fixture calls WithHttpInteractions, and the comment in the sample is explicit that this step initializes the Rust backend. That detail is easy to skim past and important to know: the mock server and the request matching that Pact performs are executed by a native component rather than by managed code, so a .NET developer is shipping and running a native binary alongside their test assembly. That also means the matching rules are applied by that backend, and the C# DSL is a front for it.
The builder itself is where the interaction is described, and the shape is consistent through the whole chain: a description of what is received, a provider state, the request, and the expected response.
this.pactBuilder
.UponReceiving("A GET request to retrieve the something")
.Given("There is a something with id 'tester'")
.WithRequest(HttpMethod.Get, "/somethings/tester")
.WithHeader("Accept", "application/json")
.WillRespond()
.WithStatus(HttpStatusCode.OK)The Given clause is the provider state, which is the bridge between the two sides: the provider side has to arrange that state before it can verify the interaction. The tests use the Arrange, Act, Assert pattern with xUnit, and VerifyAsync takes a callback that runs the consumer's own client code against the mock server, so the assertion is made against real request and response objects rather than against a recorded fixture.
Provider verification boots the real service and replays the pact
The provider side works in the opposite direction. A provider test takes one or more pact files as input and verifies that the provider adheres to the contract. The documentation splits this into a fixture that starts the service and a test class that runs the verification against those pact files.
The fixture hosts the service under test on a fixed port using the generic host builder.
ServerUri = new Uri("http://localhost:9223");
server = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseUrls(ServerUri.ToString());
webBuilder.UseStartup<TestStartup>();
})
.Build();The fixture implements IDisposable and disposes the host on teardown, and the test class consumes it through IClassFixture, so the service is started once per test class rather than once per test. The verification itself is driven by a PactVerifierConfig, which the sample populates with outputters so results are captured.
Two consequences follow. First, provider verification runs against a service that must actually start in the test environment, with its dependencies reachable, which is where the cost of contract testing reappears if your provider needs a database. Second, the pact file is the source of truth for what the provider must do, so a provider that cannot satisfy a recorded consumer expectation fails the build rather than failing later in production.
The provider state has to be arranged by the provider's own code. That is where most of the implementation effort goes on the provider side, and it is why the samples/OrdersApi example includes provider states alongside matchers.
Getting pact-net in, and the version line you will land on
Installation is through NuGet, and the repository links the PactNet package page rather than giving a command. No dotnet add package line appears in the documentation, so the package reference is left to the reader.
What the documentation is clear about is version history, and this matters because 5.0 was a large step. Two upgrade guides are provided, docs/upgrading-to-4.md for moving from 3.x or lower, and docs/upgrading-to-5.md for the 4.x to 5.x transition. Older lines are still maintained as branches, release/3.x and release/4.x, so a team that cannot move immediately is not stranded.
The tagged releases show 5.0.0 on 2024-10-06, preceded by 5.0.0-beta.3 on 2024-09-08, and a patch release 5.0.1 on 2025-03-22. That sequence suggests a major version boundary that needed a beta cycle, then a follow-up patch some months later. The last push was on 2026-09-23, so the tree is still being worked on even though the newest tag is from 2025.
The repository layout is a single solution, PactNet.sln, with src/, tests/, build/, docs/ and samples/ directories, plus a LICENSE.txt at the root. Community support runs through the Pact Foundation slack workspace and a Stack Overflow tag, and there is a sixty minute tutorial repository for people who would rather not read the sample code first.
Where the approach stops working: single consumers and shared state
Contract testing is not universally the right answer, and the cost is concentrated in two places.
The first is the provider state problem. Every interaction recorded by a consumer names a state, and the provider has to implement the setup for each of those states. For a provider with a handful of straightforward endpoints this is cheap. For a provider with complicated domain rules, the provider state handlers become a second test suite with its own maintenance burden, and you have traded an integration test for a state setup suite plus a contract.
The second is that a pact file only captures what a consumer actually exercised. If no consumer ever calls a particular endpoint, no contract covers it, and the provider can break that endpoint without any test failing. The coverage argument that contract tests beat end to end tests only holds if the contracts are themselves broad, and the documentation offers no mechanism for forcing coverage of the whole API surface.
There is a second order effect on the consumer side too. A mock server built from a consumer's assumptions can be wrong in the same way the consumer's assumption is wrong. Both sides agreeing on a mistaken contract does not make the system work, and a shared contract can institutionalise a bad assumption more durably than an integration test would have surfaced, because the pact file outlives the conversation.
Matchers help here, and they are called out in the feature list as preventing brittle tests, but a matcher only loosens comparison of values within an interaction. It says nothing about whether the interaction itself is the right one to have.
Broker integration and the CI shape it implies
The stated integration target is Pact Broker or PactFlow, and this is the part of the workflow that decides whether contract testing pays off. The consumer publishes its pact files, the provider pulls them, and verification results flow back. That is what turns a local test artefact into a shared, versioned contract with a status.
The workflow this implies is a pipeline where the consumer's build publishes a new pact whenever the consumer's expectations change, and the provider's build is re-run against the latest set of pacts from every consumer that depends on it. When a provider release would break a consumer, the provider's verification job fails before the change reaches production, rather than after a consumer deploys and discovers the mismatch.
That is a real change to how teams coordinate, and it is the cost that does not show up in the library. Pact Broker and PactFlow are the components that hold the contracts, the versioning and the results, and they are not part of pact-net itself. A team that wants the workflow but does not want to run a broker has the library without the coordination benefit.
The event-driven side deserves a mention too. The feature list states support for event-driven systems, and the samples folder uses both HTTP and message interactions, but the consumer example in the README shows only the HTTP path, so the message interaction API is best learned from the samples rather than from the top level documentation.
Editorial conclusion
pact-net suits a .NET team whose services are consumed by other teams, where a shared contract file and a mock server let frontend and backend work proceed without a joint integration environment. It does not suit a single service with one internal caller, because the broker, the pact lifecycle and the provider verification step cost more to set up than the shared integration test they replace, and the matching rules still require both sides to agree on a contract vocabulary. Before adopting it, check the upgrade guide for your current line, since docs/upgrading-to-5.md exists for the 4.x to 5.x jump and older release branches are kept at release/3.x and release/4.x, and confirm your build environment can run the Rust backend that WithHttpInteractions initialises.
Frequently asked questions
What is the difference between a consumer test and a provider test in pact-net?
A consumer test states the consumer's assumptions about the provider API, runs against a mock server and produces a contract file that is shared with the provider. A provider test takes one or more pact files as input and verifies that the provider honours them, failing if the behaviour has drifted.
How do I install pact-net?
The package is published on NuGet as PactNet and the repository links the package page for installation. The documentation does not give a dotnet add package command, so the package reference is left to the reader.
What does WithHttpInteractions do in a pact-net consumer test?
It returns the HTTP interaction builder used to define the contract, and the sample comments note that it initializes the Rust backend. The matching and mock server behaviour therefore runs through that native component rather than through managed code.
How do I upgrade pact-net from 4.x to 5.x?
The repository provides docs/upgrading-to-4.md for moving from 3.x or lower and docs/upgrading-to-5.md for the 4.x to 5.x transition. Older lines are kept as branches at release/3.x and release/4.x if you cannot move immediately.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pact-foundation-pact-net)Was this useful?