Manikandan — Manikandan
Microservices

Day 51: Consumer-Driven Contract Test

ManikandanManikandan
20 min read·Updated Sep 29, 2022

A consumer-driven contract test lets the team that *calls* an API write down exactly what it needs from that API (requests it sends, response fields it reads).

Intro

A consumer-driven contract test lets the team that calls an API write down exactly what it needs from that API (requests it sends, response fields it reads). That written expectation, the contract, is then replayed against the real provider in the provider’s own CI pipeline. If the provider changes something a consumer relies on, the provider’s build fails before release, not in production. It replaces slow, flaky end-to-end suites with fast, isolated tests on both sides, and the standard tool for it is Pact (PactNet for .NET, Pact JS for Angular/TypeScript).

Versions used in this lesson: .NET 10 (current LTS), Angular 22 (current major, released 3 Jun 2026), PactNet 5.x (Pact Specification v4 support), SQL Server / PostgreSQL as the data stores behind the services.

Running example: an insurance claims system. ClaimsService (consumer) calls PolicyService (provider) to check that a policy is active and what its coverage limit is before it accepts a claim.


Why we need this

In a microservices system the API between two services is a promise made by one team and depended upon by another. Nothing in a normal build enforces that promise. The provider team can rename a field, change a status code, or make an optional field required and every one of their unit tests still passes. The break appears later, in an integration environment or in production, when the consumer calls the new version.

The usual answer is a shared end-to-end environment with all services deployed. That answer scales badly: one broken service blocks every team, tests take tens of minutes, failures are hard to attribute, and environments drift from production. Consumer-driven contracts move the “do these two services still understand each other?” question out of a shared environment and into each team’s own pipeline, where it runs in seconds.

Business reasons: faster releases (teams deploy independently), fewer production incidents caused by API drift, and lower cost than maintaining a full integration environment.

What problem it solves

Problem (from the topic list): breaking provider API changes fail silently until production.

Concrete example from the claims system. PolicyService returns:

{ "policyNumber": "POL-100234", "status": "Active", "coverageLimit": 50000.00, "currency": "USD" }

A PolicyService developer decides coverageLimit should be an object { "amount": 50000, "currency": "USD" } so the flat currency field can go. Their unit tests are updated and pass. They deploy. ClaimsService still deserialises coverageLimit as a decimal, throws a JsonException, and every new claim returns HTTP 500. Nobody noticed because ClaimsService’s own tests use a hand-written stub that still returns the old shape.

Without contract tests you get: stubs that drift from reality (consumer tests green, production red), a fear of changing any API, and defensive “never remove a field” policies that leave APIs cluttered forever.

With contract tests, the PolicyService pipeline replays ClaimsService’s recorded expectations and fails at pull request time with a message naming the consumer and the exact mismatch.

When it is needed (and when it is NOT)

It fits when:

  • Two or more teams own the consumer and the provider separately and release independently.
  • There are many consumers of one provider (for example PolicyService is called by ClaimsService, BillingService and the Angular portal’s BFF). The provider needs to know who uses which field.
  • Your end-to-end suite is the bottleneck for releases.
  • You want to safely remove or change fields (contracts show that nobody reads them any more).
  • Service-to-service HTTP/JSON or message contracts (Pact supports message pacts for events on Azure Service Bus, Kafka, and so on).

It is overkill or wrong when:

  • One team owns both sides and they deploy together in one pipeline. A normal integration test is simpler.
  • A modular monolith with in-process calls. The compiler already checks the “contract”.
  • Public APIs with unknown consumers. You cannot get contracts from consumers you do not know; use OpenAPI diffing (for example breaking-change detection on the spec) or provider-driven contracts instead.
  • It is used to test business logic. A contract checks shape and basic behaviour (“given a policy exists, GET returns 200 with these fields”), not whether the coverage calculation is right. Business rules belong in the provider’s own tests.
  • The team will not run the broker workflow (publish, verify, can-i-deploy). Contract files that nobody verifies are just documentation.

How to identify the problem (key signals)

  1. “Works in dev, 500 in production” after a provider deploy, with the stack trace in the consumer showing deserialisation or null-reference errors on a field that changed.
  2. End-to-end suite takes 30+ minutes and fails randomly, and the usual fix is “re-run it”.
  3. Consumer tests rely on hand-written stubs or WireMock files that nobody has updated since they were created.
  4. Provider developers ask in chat “does anyone still use field X?” and nobody can answer with certainty.
  5. Release trains and freeze windows exist because teams cannot deploy independently.
  6. Version-pinning workarounds: /v1, /v2, /v2-new endpoints kept forever because no one dares remove the old one.
  7. Spike in 4xx/5xx between two internal services right after a deploy in Application Insights, and the correlation is visible on the deployment marker.

Flow Diagram

Consumer expectations are recorded, shared through a broker, and verified by the provider.

flowchart LR
CT["ClaimsService consumer test"] --> MOCK["Pact mock server"]
MOCK --> PACT["Pact file"]
PACT -- "publish SHA + branch" --> BR[("Pact Broker")]
BR -- "webhook" --> PV["PolicyService provider verification"]
PV -- "results" --> BR
BR --> CID{"can-i-deploy?"}
CID -- "yes" --> DEP["Deploy + record-deployment"]
CID -- "no" --> STOP["Block release"]

Level 1: Beginner

Analogy: a restaurant order slip. The waiter (consumer) writes down exactly what the customer asked for. The kitchen (provider) is checked against the slips: “for every slip you received, can you still cook it?” The kitchen can change its menu freely as long as every slip in circulation can still be filled.

The flow in four steps:

  1. The consumer writes a test that says “when I send this request, I expect a response with these fields”.
  2. Pact runs a mock provider, the consumer’s real HTTP client calls it, and Pact records the interaction to a JSON file (the pact or contract).
  3. The pact file is shared (file, or a Pact Broker).
  4. The provider’s pipeline replays each recorded request against the real provider and checks the response satisfies the expectations.

Minimal consumer client (ClaimsService), .NET 10:

using System.Net;
using System.Net.Http.Json;
public sealed record PolicyCoverage(
string PolicyNumber, string Status, decimal CoverageLimit, string Currency);
public sealed class PolicyApiClient(HttpClient http)
{
public async Task<PolicyCoverage?> GetCoverageAsync(
string policyNumber, CancellationToken ct = default)
{
using var response = await http.GetAsync($"/policies/{policyNumber}/coverage", ct);
if (response.StatusCode == HttpStatusCode.NotFound) return null;
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<PolicyCoverage>(ct);
}
}

Minimal consumer contract test (xUnit + PactNet 5.x):

using System.Net;
using PactNet;
using PactNet.Matchers;
using Xunit;
public class PolicyApiContractTests
{
private readonly IPactBuilderV4 _pact;
public PolicyApiContractTests()
{
var config = new PactConfig { PactDir = "../../../pacts/" };
_pact = Pact.V4("ClaimsService", "PolicyService", config).WithHttpInteractions();
}
[Fact]
public async Task GetCoverage_ForActivePolicy_ReturnsLimit()
{
_pact.UponReceiving("a request for coverage of an active policy")
.Given("policy POL-100234 exists and is active")
.WithRequest(HttpMethod.Get, "/policies/POL-100234/coverage")
.WithHeader("Accept", "application/json")
.WillRespond()
.WithStatus(HttpStatusCode.OK)
.WithHeader("Content-Type", "application/json; charset=utf-8")
.WithJsonBody(new
{
policyNumber = Match.Type("POL-100234"),
status = Match.Regex("Active", "^(Active|Lapsed|Cancelled)$"),
coverageLimit = Match.Type(50000.00m),
currency = Match.Regex("USD", "^[A-Z]{3}$")
});
await _pact.VerifyAsync(async ctx =>
{
using var http = new HttpClient { BaseAddress = ctx.MockServerUri };
http.DefaultRequestHeaders.Add("Accept", "application/json");
var client = new PolicyApiClient(http);
var result = await client.GetCoverageAsync("POL-100234");
Assert.NotNull(result);
Assert.Equal("Active", result!.Status);
});
}
}

Two things to notice. First, the test uses the real PolicyApiClient, so the contract records what the client really sends and reads. Second, matchers (Match.Type, Match.Regex) say “any value of this type”, so the contract does not break when test data changes.

Running it writes pacts/ClaimsService-PolicyService.json.

Level 2: Intermediate

6.1 Provider verification in a real .NET service

PactNet’s verifier calls the provider over a real TCP port, so an in-memory TestServer from WebApplicationFactory is not enough by itself. The common pattern is to start Kestrel on a local port in the test, with a small middleware that handles provider states.

Provider states are the “Given …” lines. Before replaying each interaction, Pact POSTs the state name to your endpoint so you can seed data.

PolicyService.ContractTests/ProviderStateMiddleware.cs
using System.Text.Json;
public sealed class ProviderStateMiddleware(RequestDelegate next, IServiceProvider sp)
{
private sealed record ProviderState(string State);
private readonly Dictionary<string, Func<IServiceProvider, Task>> _states = new()
{
["policy POL-100234 exists and is active"] = async sp =>
{
var db = sp.GetRequiredService<PolicyDbContext>();
db.Policies.RemoveRange(db.Policies);
db.Policies.Add(new Policy("POL-100234", "Active", 50000.00m, "USD"));
await db.SaveChangesAsync();
}
};
public async Task InvokeAsync(HttpContext ctx)
{
if (ctx.Request.Path != "/provider-states") { await next(ctx); return; }
var body = await JsonSerializer.DeserializeAsync<ProviderState>(
ctx.Request.Body,
new JsonSerializerOptions(JsonSerializerDefaults.Web));
if (body is not null && _states.TryGetValue(body.State, out var seed))
{
using var scope = sp.CreateScope();
await seed(scope.ServiceProvider);
}
ctx.Response.StatusCode = StatusCodes.Status200OK;
}
}
PolicyService.ContractTests/PolicyApiProviderTests.cs
using PactNet;
using PactNet.Infrastructure.Outputters;
using PactNet.Verifier;
using Xunit;
using Xunit.Abstractions;
public class PolicyApiProviderTests : IAsyncLifetime
{
private readonly ITestOutputHelper _output;
private WebApplication? _app;
private static readonly Uri BaseUri = new("http://localhost:9223");
public PolicyApiProviderTests(ITestOutputHelper output) => _output = output;
public async Task InitializeAsync()
{
var builder = WebApplication.CreateBuilder();
builder.WebHost.UseUrls(BaseUri.ToString());
// Use a test database (SQLite in-memory, Testcontainers SQL Server or PostgreSQL).
builder.Services.AddPolicyServiceForTests();
_app = builder.Build();
_app.UseMiddleware<ProviderStateMiddleware>();
_app.MapPolicyEndpoints();
await _app.StartAsync();
}
public async Task DisposeAsync()
{
if (_app is not null) await _app.DisposeAsync();
}
[Fact]
public void PolicyService_HonoursClaimsServiceContract()
{
var config = new PactVerifierConfig
{
Outputters = new List<IOutput> { new XunitOutput(_output) }
};
using var verifier = new PactVerifier("PolicyService", config);
verifier
.WithHttpEndpoint(BaseUri)
.WithFileSource(new FileInfo("../../../../ClaimsService.Tests/pacts/ClaimsService-PolicyService.json"))
.WithProviderStateUrl(new Uri(BaseUri, "/provider-states"))
.Verify();
}
}

XunitOutput is a tiny class implementing IOutput that forwards to ITestOutputHelper (the PactNet docs show it). Provider states must be idempotent and must reset the data they touch.

6.2 Sharing pacts through a Pact Broker

A file path only works on one machine. In a real setup the consumer pipeline publishes the pact to a Pact Broker (self-hosted or PactFlow), tagged with the git commit as the version and the branch name. The provider pipeline fetches the pacts it must satisfy from the broker and publishes verification results back. Sketch of the provider side (check option names against your installed PactNet 5.x, they are the part most likely to differ between minor versions):

verifier
.WithHttpEndpoint(BaseUri)
.WithProviderStateUrl(new Uri(BaseUri, "/provider-states"))
.WithPactBrokerSource(new Uri(Environment.GetEnvironmentVariable("PACT_BROKER_URL")!), options =>
{
options.TokenAuthentication(Environment.GetEnvironmentVariable("PACT_BROKER_TOKEN")!);
options.PublishResults(providerVersion, results => results.ProviderBranch(branch));
options.ConsumerVersionSelectors(
new ConsumerVersionSelector { MainBranch = true },
new ConsumerVersionSelector { DeployedOrReleased = true });
})
.Verify();

Consumer selectors matter: verify the consumer’s main branch and whatever is currently deployed or released, not every historical pact.

6.3 The Angular side

If the Angular app calls the Claims API directly (or a BFF), it is a consumer too. Pact JS (@pact-foundation/pact) works the same way: write the test around the real HttpClient service, let Pact’s mock server answer, and publish the pact under the consumer name ClaimsPortal. Keep the contract about what the UI actually reads (for example claimNumber, status, submittedOn), not the full DTO.

6.4 The CI/CD gate

  1. Consumer pipeline: run contract tests, publish pact to broker with the commit SHA and branch.
  2. Provider pipeline: verify pacts, publish results.
  3. Before deploy, each side asks the broker can-i-deploy for the target environment. After deploy, it records the deployment.
Terminal window
# Before deploying PolicyService version $GIT_SHA to production
pact-broker can-i-deploy --pacticipant PolicyService --version $GIT_SHA --to-environment production
# After a successful deploy
pact-broker record-deployment --pacticipant PolicyService --version $GIT_SHA --environment production

Level 3: Advanced

Performance. Contract tests are unit-test speed: the consumer side needs only a local mock server, the provider side needs the service plus a test database. Keep the provider’s data setup minimal in provider states; a state that restores a full database backup will make verification minutes long.

Scalability across many services. With N consumers and one provider, the provider verifies N pacts. Use consumer version selectors to limit the set, and use webhooks so that a new or changed pact triggers only the affected provider’s verification build.

Pending pacts and WIP pacts. Turn on “pending pacts” so a brand-new consumer contract that the provider has never satisfied does not break the provider’s build; it is reported instead. “Work in progress” pacts let providers see contracts from feature branches early. This avoids the deadlock where a consumer adds a new expectation and the provider is instantly red.

Security.

  • Never put real customer data in pacts. Contract files are stored in the broker and are widely readable; use synthetic policy numbers and names.
  • Broker tokens (read/write) belong in a secret store, not in pipeline YAML.
  • Restrict who can publish pacts and who can record deployments; a compromised token could mark a broken version “safe”.
  • Contracts must not include real authentication tokens. Model auth as a header matcher (Match.Regex on Bearer ...) or handle it in the provider’s test setup.

Failure modes and common mistakes.

MistakeEffectFix
Exact values everywhere instead of matchersContract breaks when test data changes; false failuresUse Match.Type, Match.Regex, Match.Include for values that are not the point of the test
Contract lists every field of the responseProvider cannot ever remove an unused fieldOnly include fields the consumer actually reads
Testing business rules in contractsSlow, brittle, duplicates provider testsAssert shape, status codes, and headers; keep logic in provider tests
Mocking the HTTP client instead of using the real one in the consumer testContract no longer reflects what the client really sendsAlways run the real client against Pact’s mock server
Skipping can-i-deployContracts verified but nothing stops an unsafe releaseMake it a required pipeline step
Provider states that depend on each other’s dataOrder-dependent, flaky verificationEach state sets up and resets its own data
One giant pact for the whole APIHard to review and to trace failuresOne interaction per behaviour, described in business terms
Using contracts for a public API with unknown consumersNo contracts to verifyUse OpenAPI breaking-change checks instead

Versioning. Use the git SHA as the pacticipant version and the branch name for branches. Do not use 1.0.0 style semantic versions for pacts; the broker needs a unique version per build.

Level 4: Expert and Architect view

8.1 Alternatives compared

ApproachWho defines the contractCatchesCostBest when
Consumer-driven contracts (Pact)ConsumersBreaking changes that affect real consumers; unused fields can be removed safelyNeeds broker, discipline, provider statesInternal services, many consumers, independent teams
Provider-driven contract (OpenAPI + diff tools)ProviderBreaking changes against the spec, whether or not anyone uses that partLow; spec already existsPublic APIs, unknown consumers
Bi-directional contract testing (PactFlow feature)Provider spec + consumer pactCompares consumer expectations with the provider’s OpenAPI spec without running the providerLower effort on provider side, weaker than real replayProviders where running verification is hard
End-to-end testsTest authorWhole-system behaviour, wiring, configSlow, flaky, shared environmentA small number of critical journeys only
Service component test (Day 50)Service teamOne service’s behaviour with stubbed dependenciesFastAlways, alongside contracts
Schema registry (for events)ProducerEvent schema compatibilityBroker-side configKafka or Service Bus event streams with schema evolution rules

8.2 Patterns it combines with

  • Service Component Test (Day 50): the component test stubs dependencies. Contract tests make sure those stubs are honest.
  • Idempotent Consumer, Messaging (Days 16–17) and Domain Events (Day 11): message pacts cover event shapes on the bus.
  • API Gateway / BFF (Days 19–20): the BFF is a consumer of downstream services and a provider to the Angular app, so it has both kinds of test.
  • Strangler Fig (Day 49): put a contract around the monolith’s API first, then verify the new service against the same contract before moving traffic.
  • Deployment platform and CI/CD (Day 46): can-i-deploy is a pipeline gate.

8.3 ADR (for an architecture review)

Title: ADR-0051 Adopt consumer-driven contract testing with Pact for internal service APIs

Status: Proposed

Context: ClaimsService, BillingService and the Claims Portal BFF depend on PolicyService. Releases are gated by a shared integration environment that takes about 40 minutes and fails intermittently. Two production incidents in the last quarter came from a field change in PolicyService that no test detected.

Decision: Consumers will write Pact contract tests (PactNet 5.x for .NET, Pact JS for Angular). Contracts are published to a Pact Broker. Providers verify pacts in CI. Deployments to any environment require a passing can-i-deploy and are recorded in the broker.

Consequences:

  • (+) Provider changes are checked against real consumer usage in the provider’s own pull request.
  • (+) Shared integration environment can shrink to a few smoke tests.
  • (+) Fields with no consumers can be removed with evidence.
  • (-) New infrastructure to run (broker, database) and new skills to teach.
  • (-) Teams must maintain provider states.
  • (-) Contracts cover shape, not business correctness; component and unit tests are still required.

Alternatives considered: OpenAPI diffing only (rejected: cannot tell which fields consumers use), keep end-to-end suite (rejected: cost and flakiness), bi-directional contracts (kept as fallback for third-party providers we cannot run).

Azure implementation

Azure has no native consumer-driven contract testing service. You host the Pact Broker yourself or use the SaaS (PactFlow), and run the tests inside your Azure-based CI/CD.

Azure services that support it:

  • Azure Pipelines or GitHub Actions run consumer tests, publish pacts, run provider verification, then can-i-deploy and record-deployment.
  • Azure Container Apps (or Azure Kubernetes Service, or App Service for Containers) to host the open-source Pact Broker container image (pactfoundation/pact-broker).
  • Azure Database for PostgreSQL – Flexible Server as the broker’s database (the broker supports PostgreSQL and MySQL; PostgreSQL is the documented default choice).
  • Azure Key Vault for the broker’s read/write tokens, database password, and basic-auth credentials. Reference them from Container Apps secrets and from pipeline variable groups or GitHub environments.
  • Azure Container Registry if you mirror the broker image or the Pact CLI image.
  • Azure Monitor / Application Insights and Log Analytics for broker availability and pipeline telemetry; add a deployment marker so provider regressions can be correlated with releases.
  • Microsoft Entra ID in front of the broker for single sign-on to the web UI (via a reverse proxy or Container Apps authentication), while CI uses tokens.

How to configure (outline):

  1. Create a PostgreSQL Flexible Server and a pact_broker database; allow only the Container Apps environment (private access through a VNet, or restricted firewall rules).
  2. Deploy the broker container with environment variables: PACT_BROKER_DATABASE_URL, PACT_BROKER_BASIC_AUTH_USERNAME / PACT_BROKER_BASIC_AUTH_PASSWORD (or PACT_BROKER_ALLOW_PUBLIC_READ set deliberately), read-only and read-write tokens from Key Vault. Enable ingress over HTTPS only.
  3. Set minReplicas to 1; the broker is a low-traffic web app but pipelines fail if it is cold or down.
  4. In the pipeline, store PACT_BROKER_BASE_URL and PACT_BROKER_TOKEN as secure variables.
  5. Add webhooks in the broker that call the Azure DevOps or GitHub API to trigger provider verification builds when a contract changes.
  6. Back up the PostgreSQL server with the built-in automated backups; the broker database is the only stateful part.

Pricing and tier considerations (verify current prices on the Azure pricing calculator and the PactFlow site before budgeting):

  • Self-hosting on Azure: cost is dominated by the database and the container. Container Apps on the Consumption plan bills for vCPU and memory used plus requests, with monthly free grants, and a broker with minReplicas: 1 at small size is inexpensive. PostgreSQL Flexible Server on a Burstable tier (B-series) is sufficient for the broker’s load in most organisations; use General Purpose only for very large contract volumes or strict high-availability needs (zone-redundant HA is available on General Purpose and above, not on Burstable).
  • PactFlow (SaaS): at the time of writing, the public price page shows a free Starter plan (unlimited users, 2 integrations, 2 webhooks), a Team plan (about $127 per month billed monthly, or $115.42 per month billed annually, with 50 integrations and 50 webhooks), and Enterprise on request (unlimited integrations, SAML SSO, role-based access). PactFlow is not an Azure service; it is a separate vendor.
  • Decision rule: small team or proof of concept → PactFlow Starter or Team. Strict data-residency rules or many teams and you already run Container Apps or AKS → self-host on Azure.

Reference architecture (text):

Developer pushes to a feature branch. The consumer pipeline (Azure Pipelines or GitHub Actions) builds ClaimsService, runs the Pact consumer tests, and publishes the pact to the Pact Broker running on Azure Container Apps (database: PostgreSQL Flexible Server; secrets: Key Vault). The broker fires a webhook that starts the PolicyService pipeline. That pipeline starts PolicyService with a Testcontainers-provided SQL Server or PostgreSQL, replays the pacts, and publishes verification results to the broker. Before each deployment stage (test, staging, production), the release job calls can-i-deploy for the target environment; after a successful deploy it calls record-deployment. Application Insights receives deployment markers from the same pipeline so operations staff can link an incident to a release.

Teaching guide for my team

10.1 Explain it to a beginner in 2 minutes

“When our Claims service calls the Policy service, it expects certain fields back. Today nothing checks that Policy will keep sending them. With a contract test, the Claims team writes down what it needs, and that becomes a file. The Policy team’s build replays that file against their real service. If they rename a field that Claims needs, their build goes red before anything ships. Nobody has to deploy a whole test environment to find out.”

10.2 Explain it to an intermediate developer in 5 minutes

Cover, in order: (1) the consumer test runs the real HTTP client against Pact’s mock server and records a pact; (2) matchers make the contract about shape and type, not exact data; (3) the pact goes to a broker with the git SHA as version; (4) the provider verifies through a real HTTP port, with provider states seeding data; (5) verification results go back to the broker; (6) can-i-deploy checks both sides before release and record-deployment tells the broker what is live; (7) the limits: contracts do not test business logic, and only include what the consumer really uses.

10.3 Hands-on exercise

Task: Build a working contract between ClaimsService and PolicyService.

  1. Create PolicyApiClient and the consumer test from Level 1, but add a second interaction: a request for a policy that does not exist returns 404 (provider state “policy POL-999999 does not exist”).
  2. Generate the pact file.
  3. In PolicyService, implement /policies/{id}/coverage and the provider verification test with the state middleware from Level 2. Run it green.
  4. Now break it: rename coverageLimit to limit in PolicyService’s response DTO. Run the provider verification again.

Expected outcome: step 3 passes; step 4 fails with a Pact verification message that names the interaction (“a request for coverage of an active policy”), the missing key coverageLimit, and the consumer (ClaimsService). Restore the name and the test goes green again. Bonus: add a new field deductible to the provider response and confirm the verification still passes (adding fields is not a breaking change).

10.4 Interview-style questions

  1. Why is it called “consumer-driven”? Because the contract records what consumers actually use, so a provider is only blocked from changes that would break a real consumer, and may freely change or remove anything nobody uses.
  2. Is a contract test the same as an integration test? No. Each side is tested in isolation against a recorded agreement, so it runs quickly with no shared environment. It checks compatibility of shape and basic behaviour, not end-to-end business flow.
  3. What does can-i-deploy do? It asks the broker whether this version of a service has verified, compatible contracts with the versions currently deployed in the target environment, and the pipeline stops if the answer is no.

Mastery checklist

  • I can write a consumer test that uses the real client and produces a pact file.
  • I choose matchers deliberately and only include fields the consumer reads.
  • I can implement provider states that seed and reset their own data.
  • I can run provider verification over a real port and read a failure message.
  • I can publish pacts and verification results to a broker using the git SHA and branch as version.
  • I can explain consumer version selectors, pending pacts, and why they exist.
  • I have wired can-i-deploy and record-deployment into a pipeline.
  • I can explain when to use contracts, OpenAPI diffing, component tests, or end-to-end tests instead.

Key takeaway

A consumer-driven contract turns “we hope the other team did not break us” into an automated check in the provider’s pipeline, so services can deploy independently without a shared end-to-end environment. Keep contracts small, about what the consumer really uses, and enforce them with can-i-deploy.

Interactive Architectural Roadmaps

Explore Complete Roadmaps & Pattern Checklists

Track your learning with interactive checklists for all 23 Gang of Four patterns and modern Microservice architecture patterns.

Share:
Back to Blog

Related Posts

View All Posts
Microservices

Day 50: Service Component Test

A service component test starts one real microservice (its real controllers, validation, business logic, EF Core mappings and messaging code) inside a test process, replaces everything *outside* the service boundary...

Manikandan
Manikandan·13 min read
Microservices

Day 53: Client-Side UI Composition (Micro-Frontends)

Client-Side UI Composition splits one large single-page application into several smaller front-end applications ("micro-frontends") that are built, tested, and deployed independently by different teams.

Manikandan
Manikandan·17 min read
Microservices

Day 52: Server-Side Page Fragment Composition

Server-Side Page Fragment Composition means the server (or an edge layer in front of it) builds one HTML page by stitching together HTML fragments that are each owned, built, and deployed by a different service or team.

Manikandan
Manikandan·15 min read