Automated Contract Testing for .NET 8 Microservices with Pact, GitHub Actions, and Docker

Mahmut Sarıkaya 4 dk okuma 4 Görüntülenme 0
Automated Contract Testing for .NET 8 Microservices with Pact, GitHub Actions, and Docker

Why contract testing matters for .NET 8 microservices

When a team splits an application into dozens of .NET 8 services, integration failures become a daily risk. A recent survey of 1,200 developers showed that 42% of production incidents were caused by mismatched API contracts. Contract testing flips the problem: instead of waiting for a broken call, you verify the contract before code reaches production.

Setting up Pact in a .NET 8 service

Pact.Net 4.0 supports .NET 8 out of the box. Start by adding the NuGet packages PactNet and PactNet.AspNetCore to both consumer and provider projects:

dotnet add package PactNet --version 4.*
 dotnet add package PactNet.AspNetCore --version 4.*

The provider registers a middleware that serves the generated pact files at /pacts/provider. In Program.cs add:

var builder = WebApplication.CreateBuilder(args);
 var app = builder.Build();
 app.UsePact();
 app.MapControllers();
 app.Run();

All of this runs on the same .NET 8 runtime, so you get the latest JIT improvements and minimal memory overhead.

Writing consumer tests that drive the contract

Consumers describe the expected interaction using a fluent DSL. The following xUnit test creates a pact for an Order endpoint:

using PactNet;
 using PactNet.Matchers;
 using Xunit;
 
 public class OrderConsumerTests : IClassFixture<PactFixture>
 {
     private readonly IPactBuilderV3 _pact;
 
     public OrderConsumerTests(PactFixture fixture) => _pact = fixture.Pact;
 
     [Fact]
     public async Task GetOrder_ReturnsExpectedShape()
     {
         _pact
             .UponReceiving("A request for order 123")
             .WithRequest(HttpMethod.Get, "/orders/123")
             .WillRespond()
             .WithStatus(200)
             .WithHeader("Content-Type", "application/json")
             .WithJsonBody(new {
                 id = Match.Type(123),
                 total = Match.Decimal(99.95),
                 status = Match.Regex("Created|Processed", "Created")
             });
 
         await _pact.VerifyAsync(async ctx => {
             var client = new HttpClient { BaseAddress = new Uri(ctx.MockServerUri) };
             var response = await client.GetAsync("/orders/123");
             response.EnsureSuccessStatusCode();
         });
     }
 }

The test writes consumer-order.json to the pacts folder, which becomes the single source of truth for the provider.

Publishing contracts with Docker

Storing contracts in a shared artifact repository is optional; many teams prefer a lightweight Docker‑based pact broker. A minimal Dockerfile looks like this:

FROM mcr.microsoft.com/dotnet/aspnet:8.0
 WORKDIR /app
 COPY . .
 ENTRYPOINT ["dotnet", "PactBroker.dll"]

Build and push the image from a CI job, then run the broker container on a staging network. The consumer pipeline pushes the JSON file using a simple curl command:

curl -X PUT -H "Content-Type: application/json" \
     --data @pacts/consumer-order.json \
     http://broker.local/pacts/provider/OrderService/consumer/OrderClient/latest

Because the broker runs in Docker, the same image can be used locally for debugging and in the cloud for production validation.

Continuous integration via GitHub Actions

GitHub Actions provides a reproducible environment for both consumer and provider verification. The workflow below runs on every push to main and fails fast if any contract test breaks:

name: Contract CI

on:
  push:
    branches: [ main ]

jobs:
  pact-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '8.0.x'
      - name: Build and test contracts
        run: dotnet test --filter Category=Contract
      - name: Publish pact to broker
        run: |
          curl -X PUT -H "Content-Type: application/json" \
               --data @pacts/consumer-order.json \
               http://localhost:9292/pacts/provider/OrderService/consumer/OrderClient/latest

The --filter Category=Contract flag isolates contract tests from unit tests, keeping the pipeline fast—typically under two minutes for a five‑service solution.

Verifying provider contracts inside Docker

The provider job pulls the latest pact file from the broker and runs the Pact verifier against a running service container. A concise verification step looks like this:

using PactNet;
 using PactNet.Verifier;
 using Xunit;
 
 public class ProviderVerificationTests
 {
     [Fact]
     public async Task VerifyProviderAgainstPacts()
     {
         var verifier = new PactVerifier(new PactVerifierConfig());
         await verifier
             .ServiceProvider("OrderService", "http://localhost:5000")
             .WithFileSource("./pacts/consumer-order.json")
             .VerifyAsync();
     }
 }

Running the test inside the same Docker network guarantees that the provider URL resolves correctly, eliminating “localhost vs container” mismatches that often plague local debugging.

Best practices and common pitfalls

1. Keep contracts versioned alongside the consumer code. Tag the Git commit that produced a pact file; the broker can then enforce semantic versioning.

2. Avoid over‑specifying response bodies. Use Match.Type and Match.Regex to allow flexibility while still catching breaking changes.

3. Run provider verification on every PR, not only on the main branch. Early feedback prevents a cascade of failing deployments.

4. Remember that Docker networking isolates ports. Expose the provider on a fixed port (e.g., 5000) and reference that port in the verifier configuration.

Conclusion

Automating contract testing for .NET 8 microservices with Pact, Docker, and GitHub Actions turns brittle integration points into reliable contracts. By generating pacts from consumer tests, publishing them to a Docker‑hosted broker, and verifying the provider in a CI pipeline, teams can ship changes every few hours without fearing runtime API mismatches. The result is a faster feedback loop, reduced production incidents, and confidence that every .NET 8 service honors the shared contract.

Sources

• Pact Foundation – Official Documentation
• Microsoft Docs – .NET 8 Release Notes
• GitHub Docs – GitHub Actions Workflow Syntax

Author: Mahmut Sarıkaya — sarikayadev.com

Etiketler: #dotnet 8 #contract testing #pact framework #github actions #docker
Paylaş:
M

Yazar

Mahmut Sarıkaya

yazılım Geliştirici

Yorumlar

Henüz yorum yok. İlk yorumu siz yapın!

Yorum Bırakın

3 + 4 =