From harness-claude
Sets up Pact consumer-provider contract tests to verify API compatibility between services, prevent breaking changes in independent deployments, and replace fragile end-to-end tests.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Verify service compatibility using Pact consumer-provider contract tests
Verifies API contracts between services using Pact consumer-driven tests, provider verification, schema validation, and OpenAPI specs. Use for microservices communication, preventing breaking changes.
Verifies API contracts between services using Pact, OpenAPI, JSON Schema, and tools like REST Assured to prevent breaking changes in microservices and integrations.
Validates API contracts with Pact (JS/Python/JVM) and Spring Cloud Contract via consumer-driven testing. Prevents microservice breaking changes.
Share bugs, ideas, or general feedback.
Verify service compatibility using Pact consumer-provider contract tests
npm install -D @pact-foundation/pact
import { PactV4 } from '@pact-foundation/pact';
const provider = new PactV4({
consumer: 'OrderService',
provider: 'UserService',
});
describe('UserService API', () => {
it('returns user by ID', async () => {
await provider
.addInteraction()
.given('a user exists with ID 123')
.uponReceiving('a request for user 123')
.withRequest('GET', '/api/users/123', (builder) => {
builder.headers({ Accept: 'application/json' });
})
.willRespondWith(200, (builder) => {
builder.headers({ 'Content-Type': 'application/json' }).jsonBody({
id: '123',
name: 'Alice',
email: 'alice@example.com',
});
})
.executeTest(async (mockServer) => {
const client = new UserClient(mockServer.url);
const user = await client.getUser('123');
expect(user).toEqual({
id: '123',
name: 'Alice',
email: 'alice@example.com',
});
});
});
});
Generate the pact file — running the consumer test creates a contract file (e.g., pacts/OrderService-UserService.json).
Verify on the provider side:
import { Verifier } from '@pact-foundation/pact';
describe('Pact Verification', () => {
it('validates the OrderService contract', async () => {
const verifier = new Verifier({
providerBaseUrl: 'http://localhost:3001',
pactUrls: ['./pacts/OrderService-UserService.json'],
stateHandlers: {
'a user exists with ID 123': async () => {
await seedUser({ id: '123', name: 'Alice', email: 'alice@example.com' });
},
},
});
await verifier.verifyProvider();
});
});
// Consumer publishes
await publishPacts({
pactFilesOrDirs: ['./pacts'],
pactBroker: 'https://your-broker.pactflow.io',
consumerVersion: process.env.GIT_SHA,
tags: ['main'],
});
// Provider verifies from broker
const verifier = new Verifier({
providerBaseUrl: 'http://localhost:3001',
pactBrokerUrl: 'https://your-broker.pactflow.io',
providerVersionTags: ['main'],
publishVerificationResult: true,
});
stateHandlers: {
'no users exist': async () => {
await db.user.deleteMany();
},
'a user exists with ID 123': async () => {
await db.user.upsert({
where: { id: '123' },
create: { id: '123', name: 'Alice', email: 'alice@example.com' },
update: {},
});
},
},
import { like, eachLike, regex } from '@pact-foundation/pact';
.jsonBody({
id: like('123'), // any string
name: like('Alice'), // any string
email: regex(/.*@.*/, 'a@b.com'), // matches regex
posts: eachLike({ title: like('Post') }), // array of objects
})
Contract testing verifies that two services agree on the format of their communication. The consumer defines its expectations as a "contract," and the provider verifies it can fulfill that contract.
Consumer-driven contracts: The consumer writes the contract because it knows what it needs. The provider verifies it can deliver. This ensures the provider does not make breaking changes that affect consumers.
Contract vs integration vs E2E:
Contract tests are faster and more focused than integration tests but do not verify business logic.
Pact workflow:
Trade-offs: