Pact is the de-facto API contract testing tool. Replace expensive and brittle end-to-end integration tests with fast, reliable and easy to debug unit tests.
- β‘ Lightning fast
- π Effortless full-stack integration testing - from the front-end to the back-end
- π Supports HTTP/REST and event-driven systems
- π οΈ Configurable mock server
- π Powerful matching rules prevents brittle tests
- π€ Integrates with Pact Broker / Pactflow for powerful CI/CD workflows
- π‘ Supports 12+ languages
Why use Pact?
Contract testing with Pact lets you:
- β‘ Test locally
- π Deploy faster
- β¬οΈ Reduce the lead time for change
- π° Reduce the cost of API integration testing
- π₯ Prevent breaking changes
- π Understand your system usage
- π Document your APIs for free
- π Remove the need for complex data fixtures
- π€·ββοΈ Reduce the reliance on complex test environments
Watch our series on the problems with end-to-end integrated tests, and how contract testing can help.
Learn everything in Pact Net in 60 minutes
Looking for PactNet v3.x? See the release/3.x
branch.
- Join our community slack workspace.
- Stack Overflow: https://stackoverflow.com/questions/tagged/pact
- Say π on Twitter @pact_up
In the sections below, we provide a brief sample of the typical flow for Pact testing, written in the XUnit framework. To see the complete example and run it, check out the Samples/ReadMe
folder.
Pact is a consumer-driven contract testing tool, which is a fancy way of saying that the API Consumer
writes a test to set out its assumptions and needs of its API Provider
(s). By unit testing our API client with Pact, it will produce a contract
that we can share to our Provider
to confirm these assumptions and prevent breaking changes.
In this example, we are going to be testing our User API client, responsible for communicating with the UserAPI
over HTTP. It currently has a single method GetUser(id)
that will return a User
.
Pact tests have a few key properties. We'll demonstrate a common example using the 3A Arrange/Act/Assert
pattern.
public class SomethingApiConsumerTests
{
private readonly IPactBuilderV3 _pactBuilder;
public SomethingApiConsumerTests(ITestOutputHelper output)
{
// Use default pact directory ..\..\pacts and default log
// directory ..\..\logs
var pact = Pact.V3("Something API Consumer", "Something API");
// or specify custom configuration such as pact file directory and serializer settings
pact = Pact.V3("Something API Consumer", "Something API", new PactConfig
{
PactDir = @"..\pacts",
DefaultJsonSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}
});
// Initialize Rust backend
_pactBuilder = pact.UsingNativeBackend();
}
[Fact]
public async Task GetSomething_WhenTheTesterSomethingExists_ReturnsTheSomething()
{
// Arrange
_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)
.WithHeader("Content-Type", "application/json; charset=utf-8")
.WithJsonBody(new
{
// NOTE: These properties are case sensitive!
id = "tester",
firstName = "Totally",
lastName = "Awesome"
});
await _pactBuilder.VerifyAsync(async ctx =>
{
// Act
var client = new SomethingApiClient(ctx.MockServerUri);
var something = await client.GetSomething("tester");
// Assert
Assert.Equal("tester", something.Id);
});
}
}
A provider test takes one or more pact files (contracts) as input, and Pact verifies that your provider adheres to the contract. In the simplest case, you can verify a provider as per below. In SomethingApiFixture
, the provider is started. In SomethingApiTests
, the fixture is verified against the pact files.
public class SomethingApiFixture : IDisposable
{
private readonly IHost server;
public Uri ServerUri { get; }
public SomethingApiFixture()
{
ServerUri = new Uri("http://localhost:9222");
server = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseUrls(ServerUri.ToString());
webBuilder.UseStartup<TestStartup>();
})
.Build();
server.Start();
}
public void Dispose()
{
server.Dispose();
}
}
public class SomethingApiTests : IClassFixture<SomethingApiFixture>
{
private readonly SomethingApiFixture fixture;
private readonly ITestOutputHelper output;
public SomethingApiTests(SomethingApiFixture fixture, ITestOutputHelper output)
{
this.fixture = fixture;
this.output = output;
}
[Fact]
public void EnsureSomethingApiHonoursPactWithConsumer()
{
//Arrange
var config = new PactVerifierConfig
{
Outputters = new List<IOutput>
{
// NOTE: PactNet defaults to a ConsoleOutput, however
// xUnit 2 does not capture the console output, so this
// sample creates a custom xUnit outputter. You will
// have to do the same in xUnit projects.
new XUnitOutput(output),
},
};
string pactPath = Path.Combine("..",
"..",
"path",
"to",
"pacts",
"Something API Consumer-Something API.json");
// Act / Assert
IPactVerifier pactVerifier = new PactVerifier(config);
pactVerifier
.ServiceProvider("Something API", fixture.ServerUri)
.WithFileSource(new FileInfo(pactPath))
.WithProviderStateUrl(new Uri(fixture.ServerUri, "/provider-states"))
.Verify();
}
}
For writing messaging pacts instead of requests/response pacts, see the messaging pacts guide.
Due to using a shared native library instead of C# for the main Pact logic only certain OSs are supported:
OS | Arch | Support |
---|---|---|
Windows | x86 | β No |
Windows | x64 | βοΈ Yes |
Linux (libc) | ARM | β No |
Linux (libc) | x86 | β No |
Linux (libc) | x64 | βοΈ Yes |
Linux (musl) | Any | β No |
OSX | x64 | βοΈ Yes |
OSX | ARM (M1) |
Version | Stable | Spec Compatibility | Install |
---|---|---|---|
4.x | Beta | 2, 3 | See installation |
3.x | Stable | 2 |
The roadmap for Pact and Pact Net is outlined on our main website.
See CONTRIBUTING.