pramoddutta/qaskills/contract-test-generator

Trust tier 1
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Generate consumer-driven contract tests using Pact framework to verify API provider-consumer compatibility and prevent integration breaking changes

Install

npx vskill@latest install pramoddutta/qaskills/contract-test-generator

SKILL.md

Contract Test Generator

Contract testing bridges the gap between unit tests and full integration tests by verifying that services can communicate correctly without requiring all services to be running simultaneously. Consumer-driven contract testing, pioneered by the Pact framework, inverts the traditional approach: consumers define what they expect from providers, and providers verify they can satisfy those expectations. This skill guides AI coding agents through generating robust contract tests that catch integration breaking changes before they reach production.

Core Principles

  1. Consumer-Driven Design: Consumers define the contract based on what they actually use, not what the provider offers. This ensures contracts are minimal, focused, and reflect real usage patterns rather than hypothetical API surfaces.
  1. Provider Verification Independence: Provider tests verify contracts independently without needing the consumer running. This decouples deployment schedules and enables teams to work autonomously while maintaining integration guarantees.
  1. Contract as Shared Artifact: The contract (pact file) serves as a living specification between consumer and provider. It is versioned, stored centrally, and referenced by both sides during their respective CI pipelines.
  1. Minimal Assertion Surface: Contracts should assert only what the consumer needs, not the full provider response. Testing for specific fields rather than entire response bodies prevents brittle contracts that break on harmless provider changes.
  1. Versioning Alignment with Deployability: Every contract must be associated with a specific consumer version and verified against a specific provider version. The combination of these versions determines whether a deployment is safe.
  1. Fail-Fast in CI: Contract verification failures must block deployments. The can-i-deploy tool provides a definitive answer about deployment safety based on the latest verification matrix.
  1. Incremental Adoption: Contract tests can be introduced for the most critical interactions first, then expanded. There is no requirement to cover every endpoint immediately; focus on high-risk integration points.

Project Structure

project-root/
├── consumer/
│ ├── src/
│ │ ├── api-client.ts
│ │ └── types.ts
│ ├── tests/
│ │ └── contract/
│ │ ├── user-service.consumer.pact.ts
│ │ ├── order-service.consumer.pact.ts
│ │ └── helpers/
│ │ ├── pact-setup.ts
│ │ └── matchers.ts
│ ├── pacts/ # Generated pact files (JSON)
│ │ └── consumer-user_service.json
│ └── pact-config.ts
├── provider/
│ ├── src/
│ │ └── controllers/
│ ├── tests/
│ │ └── contract/
│ │ ├── provider-verification.pact.ts
│ │ └── state-handlers/
│ │ ├── user-states.ts
│ │ └── order-states.ts
│ └── pact-config.ts
├── pact-broker/
│ └── docker-compose.yml
└── ci/
├── publish-pacts.sh
├── verify-provider.sh
└── can-i-deploy.sh

Consumer Test Generation

Basic Consumer Test in TypeScript

Consumer tests define the expectations a consumer has of a provider API. The Pact mock server simulates the provider during consumer tests.

typescript
// consumer/tests/contract/user-service.consumer.pact.ts
import { PactV4, MatchersV3 } from '@pact-foundation/pact';
import { resolve } from 'path';
import { UserApiClient } from '../../src/api-client';
const { like, eachLike, regex, integer, string, timestamp } = MatchersV3;
const provider = new PactV4({
consumer: 'frontend-app',
provider: 'user-service',
dir: resolve(__dirname, '../../pacts'),
logLevel: 'warn',
});
describe('User Service Contract', () => {
describe('GET /api/users/:id', () => {
it('returns a user when one exists', async () => {
await provider
.addInteraction()
.given('a user with ID 42 exists')
.uponReceiving('a request for user 42')
.withRequest('GET', '/api/users/42', (builder) => {
builder.headers({
Accept: 'application/json',
Authorization: regex(/^Bearer\s[\w-]+\.[\w-]+\.[\w-]+$/, 'Bearer eyJ.test.token'),
});
})
.willRespondWith(200, (builder) => {
builder.headers({ 'Content-Type': 'application/json' });
builder.jsonBody({
id: integer(42),
name: string('Jane Doe'),
email: regex(/^[\w.]+@[\w.]+\.\w+$/, 'jane@example.com'),
role: regex(/^(admin|user|moderator)$/, 'user'),
createdAt: timestamp("yyyy-MM-dd'T'HH:mm:ss.SSSX", '2024-01-15T10:30:00.000Z'),
preferences: like({
theme: string('dark'),
notifications: like(true),
}),
});
})
.executeTest(async (mockServer) => {
const client = new UserApiClient(mockServer.url);
const user = await client.getUser(42);
expect(user.id).toBe(42);
expect(user.name).toBeDefined();
expect(user.email).toContain('@');
});
});
it('returns 404 when user does not exist', async () => {
await provider
.addInteraction()
.given('no user with ID 999 exists')
.uponReceiving('a request for non-existent user 999')
.withRequest('GET', '/api/users/999', (builder) => {
builder.headers({
Accept: 'application/json',
Authorization: regex(/^Bearer\s[\w-]+\.[\w-]+\.[\w-]+$/, 'Bearer eyJ.test.token'),
});
})
.willRespondWith(404, (builder) => {
builder.headers({ 'Content-Type': 'application/json' });
builder.jsonBody({
error: string('Not Found'),
message: string('User with ID 999 not found'),
});
})
.executeTest(async (mockServer) => {
const client = new UserApiClient(mockServer.url);
await expect(client.getUser(999)).rejects.toThrow('User not found');
});
});
});
describe('POST /api/users', () => {
it('creates a new user', async () => {
await provider
.addInteraction()
.given('the user creation endpoint is available')
.uponReceiving('a request to create a user')
.withRequest('POST', '/api/users', (builder) => {
builder.headers({
'Content-Type': 'application/json',
Authorization: regex(/^Bearer\s.+$/, 'Bearer eyJ.admin.token'),
});
builder.jsonBody({
name: string('John Smith'),
email: regex(/^[\w.]+@[\w.]+\.\w+$/, 'john@example.com'),
role: regex(/^(admin|user|moderator)$/, 'user'),
});
})
.willRespondWith(201, (builder) => {
builder.headers({ 'Content-Type': 'application/json' });
builder.jsonBody({
id: integer(1),
name: string('John Smith'),
email: string('john@example.com'),
role: string('user'),
createdAt: timestamp("yyyy-MM-dd'T'HH:mm:ss.SSSX", '2024-06-01T12:00:00.000Z'),
});
})
.executeTest(async (mockServer) => {
const client = new UserApiClient(mockServer.url);
const created = await client.createUser({
name: 'John Smith',
email: 'john@example.com',
role: 'user',
});
expect(created.id).toBeDefined();
expect(created.name).toBe('John Smith');
});
});
});
});
Consumer Test in Java
java
// consumer/src/test/java/com/example/contract/UserServiceConsumerPactTest.java
package com.example.contract;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
import au.com.dius.pact.consumer.junit5.PactTestFor;
import au.com.dius.pact.consumer.MockServer;
import au.com.dius.pact.core.model.V4Pact;
import au.com.dius.pact.core.model.annotations.Pact;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(PactConsumerTestExt.class)
@PactTestFor(providerName = "user-service", port = "8080")
public class UserServiceConsumerPactTest {
@Pact(consumer = "order-service")
public V4Pact getUserPact(PactDslWithProvider builder) {
return builder
.given("a user with ID 42 exists")
.uponReceiving("a request for user 42")
.path("/api/users/42")
.method("GET")
.headers("Accept", "application/json")
.willRespondWith()
.status(200)
.headers(Map.of("Content-Type", "application/json"))
.body(new PactDslJsonBody()
.integerType("id", 42)
.stringType("name", "Jane Doe")
.stringMatcher("email", "^[\\w.]+@[\\w.]+\\.\\w+$", "jane@example.com")
.stringType("role", "user")
.object("address")
.stringType("city", "Portland")
.stringType("state", "OR")
.closeObject()
)
.toPact(V4Pact.class);
}
@Test
@PactTestFor(pactMethod = "getUserPact")
void testGetUser(MockServer mockServer) {
UserApiClient client = new UserApiClient(mockServer.getUrl());
User user = client.getUser(42);
assertThat(user.getId()).isEqualTo(42);
assertThat(user.getName()).isNotBlank();
assertThat(user.getEmail()).contains("@");
}
}

Provider Verification Setup

TypeScript Provider Verification
typescript
// provider/tests/contract/provider-verification.pact.ts
import { Verifier } from '@pact-foundation/pact';
import { resolve } from 'path';
import { app } from '../../src/app';
import { Server } from 'http';
let server: Server;
const PORT = 8081;
beforeAll(async () => {
server = app.listen(PORT);
});
afterAll(async () => {
server.close();
});
describe('Provider Verification', () => {
it('validates the expectations of all consumers', async () => {
const verifier = new Verifier({
providerBaseUrl: `http://localhost:${PORT}`,
provider: 'user-service',
// Option A: Verify from Pact Broker
pactBrokerUrl: process.env.PACT_BROKER_BASE_URL,
pactBrokerToken: process.env.PACT_BROKER_TOKEN,
publishVerificationResult: process.env.CI === 'true',
providerVersion: process.env.GIT_COMMIT_SHA,
providerVersionBranch: process.env.GIT_BRANCH,
// Enable pending pacts (new contracts won't break provider builds)
enablePending: true,
includeWipPactsSince: '2024-01-01',
// State handlers set up test data for each provider state
stateHandlers: {
'a user with ID 42 exists': async () => {
await seedDatabase({
users: [{ id: 42, name: 'Jane Doe', email: 'jane@example.com', role: 'user' }],
});
},
'no user with ID 999 exists': async () => {
await clearDatabase('users');
},
'the user creation endpoint is available': async () => {
await clearDatabase('users');
await resetSequences('users');
},
},
// Request filter to add auth headers the provider requires
requestFilter: (req, _res, next) => {
req.headers['Authorization'] = `Bearer ${generateTestToken()}`;
next();
},
});
await verifier.verifyProvider();
});
});
Java Provider Verification
java
@Provider("user-service")
@PactBroker(
url = "${PACT_BROKER_BASE_URL}",
authentication = @PactBrokerAuth(token = "${PACT_BROKER_TOKEN}")
)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserServiceProviderPactTest {

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 76 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
116
Repository forks
4
7-day trend score
24

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 9 May 2026, 5 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/pramoddutta/qaskills/contract-test-generator/badge)](https://verified-skill.com/skills/pramoddutta/qaskills/contract-test-generator)

Something wrong with this skill or its page? Tell us.