PactEf.Verify
0.1.0-beta.1
dotnet add package PactEf.Verify --version 0.1.0-beta.1
NuGet\Install-Package PactEf.Verify -Version 0.1.0-beta.1
<PackageReference Include="PactEf.Verify" Version="0.1.0-beta.1" />
<PackageVersion Include="PactEf.Verify" Version="0.1.0-beta.1" />
<PackageReference Include="PactEf.Verify" />
paket add PactEf.Verify --version 0.1.0-beta.1
#r "nuget: PactEf.Verify, 0.1.0-beta.1"
#:package PactEf.Verify@0.1.0-beta.1
#addin nuget:?package=PactEf.Verify&version=0.1.0-beta.1&prerelease
#tool nuget:?package=PactEf.Verify&version=0.1.0-beta.1&prerelease
PactEf
Consumer-driven contract testing for EF Core database schemas.
PactEf intercepts the SQL that consumer integration tests actually execute, writes those queries as a JSON snapshot ("pact"), and replays them via EXPLAIN against a fresh database at migration time. If a migration breaks a query — renamed column, dropped table, type change — the verification test fails before the change ships.
How It Works
Consumer tests run Producer runs migrations
| |
v v
EF Core interceptor Testcontainers spins up
captures SQL queries a fresh Postgres instance
| |
v v
SampleConsumer.json -----> PactEfVerifier replays
(snapshot / pact) each query via EXPLAIN
|
Pass / Fail report
Packages
| Package | Purpose |
|---|---|
PactEf.Core |
Shared models: QueryEntry, SnapshotFile, SnapshotSerializer |
PactEf.Capture |
EF Core interceptor that records SQL during consumer tests |
PactEf.Verify |
Loads snapshots and verifies them against the current schema |
Quick Start
1. Consumer side — capture queries
Add PactEf.Capture to your consumer test project and wire up the interceptor:
// In your test fixture
var options = new DbContextOptionsBuilder<MyDbContext>()
.UseNpgsql(connectionString)
.AddPactEfCapture(o => o.ConsumerName = "MyConsumer")
.Options;
Register the assembly fixture so snapshots are flushed after the test run:
// AssemblyInfo.cs
[assembly: TestFramework("Xunit.Extensions.AssemblyFixture.XunitTestFramework",
"Xunit.Extensions.AssemblyFixture")]
// Test class
public class MyTests : IAssemblyFixture<PactEfAssemblyFixture>
Set the environment variable so capture activates:
export DOTNET_ENVIRONMENT=Testing
# or ASPNETCORE_ENVIRONMENT=Testing
Snapshots are written to pactef-snapshots/<ConsumerName>.json next to the test project.
2. Producer side — verify against current schema
Add PactEf.Verify to your database project and add a verification test:
[Fact]
[Trait("Category", "PactEfVerification")]
public async Task AllConsumerSnapshots_AreCompatibleWithCurrentSchema()
{
await PactEfVerifier.VerifyAllAsync(options =>
{
options.SnapshotSources =
[
SnapshotSource.FromFolder("/path/to/consumers/my-consumer/pactef-snapshots"),
SnapshotSource.FromEnvVariable("PACTEF_SNAPSHOT_PATHS"), // local override
];
options.ConnectionString = _container.GetConnectionString();
options.Provider = DbProvider.PostgreSql;
options.DefaultMode = VerificationMode.Explain;
});
}
The test spins up a real database (Testcontainers), applies all migrations, then runs EXPLAIN for each captured query. Any column/table/type mismatch fails the test with a detailed report.
Environment Variables
| Variable | Default | Purpose |
|---|---|---|
ASPNETCORE_ENVIRONMENT |
— | Must equal Testing for capture to activate |
PACTEF_CAPTURE_DISABLED |
— | Set to any value to disable capture regardless of environment |
PACTEF_SNAPSHOT_PATHS |
— | Semicolon-separated absolute paths; overrides FromFolder sources for local dev |
Snapshot Format
Current format is v2 ("schemaVersion": "2.0").
{
"schemaVersion": "2.0",
"consumerName": "SampleConsumer",
"capturedAt": "2026-05-18T16:42:30Z",
"dbSchemaVersion": "20260514000000_InitialCreate",
"queries": [
{
"sql": "INSERT INTO \"OrderItems\" (\"Description\", \"OrderId\")\nVALUES (@p0, @p1)\nRETURNING \"Id\";\n",
"parameterTypes": ["String", "Int32"],
"parameters": [
{
"name": "@p0",
"clrType": "String",
"dbType": "String",
"storeType": "Varchar",
"maxLength": 1000,
"isNullable": true
},
{
"name": "@p1",
"clrType": "Int32",
"dbType": "Int32",
"storeType": "Integer",
"isNullable": false
}
],
"executionCount": 1
}
]
}
parameters[] (v2 metadata) map 1:1 to ParameterMetadata: name, clrType, dbType, storeType,
maxLength, precision, scale, isNullable, size. All fields nullable — null means unknown, not
unconstrained; a missing maxLength/isNullable just means capture couldn't resolve the column, not that
it's unbounded/NOT NULL.
name matters: parameters order follows DbCommand.Parameters order, which is not SQL appearance
order (e.g. EF Core emits UPDATE ... SET "C" = @p0 WHERE "Id" = @p1 with @p1 bound first). Verification
matches parameters to placeholders by name, falling back to position only for legacy v1 snapshots or
unnamed/ambiguous params — matching positionally in the general case can substitute a boundary literal into
the wrong column.
parameterTypes is still written for backward compatibility but is legacy; parameters is authoritative.
Old v1 snapshots (no parameters) still load and verify — SnapshotSerializer backfills one
ParameterMetadata per entry from parameterTypes (only ClrType set, no boundary variants from the
consumer side).
Verification Modes
| Mode | Behaviour |
|---|---|
Explain |
Runs EXPLAIN <sql> — catches schema errors without touching data. Default. |
Execute |
Runs the query with substituted literal values — catches runtime errors too. |
Repository Layout
src/
PactEf.Core/ Models and serialization
PactEf.Core.Tests/
PactEf.Capture/ EF Core interceptor, xUnit fixtures
PactEf.Capture.Tests/
PactEf.Verify/ Snapshot loader, verifier, failure report
PactEf.Verify.Tests/
samples/
SampleDb/ Entity model, migrations, SchemaVerificationTests
SampleConsumer/ OrderRepository using SampleDb
SampleConsumer.Tests/ Integration tests that capture SQL
Running Tests
# Unit tests (fast, no Docker required)
dotnet test src/PactEf.Core.Tests/PactEf.Core.Tests.csproj
dotnet test src/PactEf.Capture.Tests/PactEf.Capture.Tests.csproj
dotnet test src/PactEf.Verify.Tests/PactEf.Verify.Tests.csproj
# Consumer integration tests (requires Docker — captures snapshot)
dotnet test samples/SampleConsumer.Tests/SampleConsumer.Tests.csproj
# Schema verification (requires Docker — verifies snapshot against schema)
export PACTEF_SNAPSHOT_PATHS=/absolute/path/to/samples/SampleConsumer.Tests/pactef-snapshots
dotnet test samples/SampleDb/SampleDb.csproj --filter "Category=PactEfVerification"
Stack
- .NET 10, C# 13
- EF Core 9 (interceptors)
- Npgsql / PostgreSQL
- xUnit v2 +
Xunit.Extensions.AssemblyFixture - Testcontainers.PostgreSql (samples only)
- System.Text.Json
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Npgsql (>= 10.0.3)
- PactEf.Core (>= 0.1.0-beta.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-beta.1 | 63 | 8/18/2026 |