Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Consumer-driven contract testing with Pact framework. Generate consumer contracts, configure Pact Broker publishing, execute provider verification, detect breaking changes, and integrate with CI/CD pipelines.
.claude/skills/a5c-ai-pact-contract-testing/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 225% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 96% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 214% | 0% |
| case-19 | ✗→✓ | ▲ Improved | 225% | 0% |
| case-25 | ✗→✓ | ▲ Improved | 92% | 0% |
You are pact-contract-testing - a specialized skill for consumer-driven contract testing with the Pact framework, enabling reliable API integration testing between services.
This skill enables AI-powered contract testing including:
Create consumer-side contracts with Pact JS:
javascriptimport { PactV3, MatchersV3 } from '@pact-foundation/pact'; const { like, eachLike, regex } = MatchersV3; const provider = new PactV3({ consumer: 'frontend-app', provider: 'user-service', logLevel: 'info' }); describe('User API Contract', () => { it('should return user by ID', async () => { // Arrange: Define expected interaction await provider .given('a user with ID 123 exists') .uponReceiving('a request for user 123') .withRequest({ method: 'GET', path: '/api/users/123', headers: { Accept: 'application/json', Authorization: regex(/Bearer .+/, 'Bearer token123') } }) .willRespondWith({ status: 200, headers: { 'Content-Type': 'application/json' }, body: { id: like(123), email: like('user@example.com'), name: like('John Doe'), createdAt: like('2024-01-15T10:30:00Z'), roles: eachLike('user') } }); // Act & Assert: Execute test await provider.executeTest(async (mockServer) => { const response = await fetch(`${mockServer.url}/api/users/123`, { headers: { Accept: 'application/json', Authorization: 'Bearer token123' } }); expect(response.status).toBe(200); const user = await response.json(); expect(user.id).toBe(123); }); }); it('should return 404 for non-existent user', async () => { await provider .given('user 999 does not exist') .uponReceiving('a request for non-existent user') .withRequest({ method: 'GET', path: '/api/users/999' }) .willRespondWith({ status: 404, body: { error: like('User not found'), code: like('USER_NOT_FOUND') } }); await provider.executeTest(async (mockServer) => { const response = await fetch(`${mockServer.url}/api/users/999`); expect(response.status).toBe(404); }); }); });
Verify provider against contracts:
javascriptimport { Verifier } from '@pact-foundation/pact'; const verifier = new Verifier({ provider: 'user-service', providerBaseUrl: 'http://localhost:3000', // Fetch pacts from broker pactBrokerUrl: 'https://your-broker.pactflow.io', pactBrokerToken: process.env.PACT_BROKER_TOKEN, // Provider version providerVersion: process.env.GIT_COMMIT || '1.0.0', providerVersionBranch: process.env.GIT_BRANCH || 'main', // State handlers stateHandlers: { 'a user with ID 123 exists': async () => { // Set up test data await db.users.create({ id: 123, email: 'user@example.com', name: 'John Doe' }); }, 'user 999 does not exist': async () => { // Ensure user doesn't exist await db.users.delete(999); } }, // Publish results publishVerificationResult: true, enablePending: true, includeWipPactsSince: '2024-01-01' }); describe('Provider Verification', () => { beforeAll(async () => { // Start provider service await startServer(); }); afterAll(async () => { await stopServer(); }); it('should verify all consumer contracts', async () => { await verifier.verifyProvider(); }); });
Publish contracts to Pact Broker:
javascriptimport { Publisher } from '@pact-foundation/pact'; const publisher = new Publisher({ pactFilesOrDirs: ['./pacts'], pactBroker: 'https://your-broker.pactflow.io', pactBrokerToken: process.env.PACT_BROKER_TOKEN, consumerVersion: process.env.GIT_COMMIT || '1.0.0', branch: process.env.GIT_BRANCH || 'main', tags: [process.env.GIT_BRANCH || 'main'] }); await publisher.publishPacts();
Verify deployment safety:
bash# Check if consumer can be deployed pact-broker can-i-deploy \ --pacticipant frontend-app \ --version $(git rev-parse HEAD) \ --to-environment production \ --broker-base-url https://your-broker.pactflow.io \ --broker-token $PACT_BROKER_TOKEN # Check if provider can be deployed pact-broker can-i-deploy \ --pacticipant user-service \ --version $(git rev-parse HEAD) \ --to-environment production \ --broker-base-url https://your-broker.pactflow.io \ --broker-token $PACT_BROKER_TOKEN # Record deployment pact-broker record-deployment \ --pacticipant user-service \ --version $(git rev-parse HEAD) \ --environment production \ --broker-base-url https://your-broker.pactflow.io \ --broker-token $PACT_BROKER_TOKEN
GitHub Actions workflow:
yamlname: Contract Tests on: push: branches: [main, develop] pull_request: branches: [main] env: PACT_BROKER_URL: https://your-broker.pactflow.io PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }} jobs: consumer-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Run consumer contract tests run: npm run test:contract:consumer - name: Publish pacts run: | npx pact-broker publish ./pacts \ --consumer-app-version ${{ github.sha }} \ --branch ${{ github.ref_name }} \ --broker-base-url $PACT_BROKER_URL \ --broker-token $PACT_BROKER_TOKEN provider-verification: runs-on: ubuntu-latest needs: consumer-tests steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Start provider run: npm run start:test & - name: Verify provider run: npm run test:contract:provider can-i-deploy: runs-on: ubuntu-latest needs: [consumer-tests, provider-verification] if: github.ref == 'refs/heads/main' steps: - name: Can I deploy? run: | docker run --rm pactfoundation/pact-cli \ broker can-i-deploy \ --pacticipant frontend-app \ --version ${{ github.sha }} \ --to-environment production \ --broker-base-url $PACT_BROKER_URL \ --broker-token $PACT_BROKER_TOKEN
Set up Pact Broker webhooks:
bash# Trigger provider verification on consumer change pact-broker create-webhook \ 'https://api.github.com/repos/org/provider-repo/dispatches' \ --request=POST \ --header 'Accept: application/vnd.github.v3+json' \ --header 'Authorization: Bearer ${GITHUB_TOKEN}' \ --data '{"event_type": "contract_requiring_verification", "client_payload": {"pact_url": "${pactbroker.pactUrl}"}}' \ --description "Trigger provider verification on contract change" \ --contract-content-changed \ --broker-base-url https://your-broker.pactflow.io \ --broker-token $PACT_BROKER_TOKEN
Use with OpenAPI specifications:
javascript// Provider publishes OpenAPI spec import { PactV3 } from '@pact-foundation/pact'; // Consumer tests against provider's published OpenAPI const provider = new PactV3({ consumer: 'frontend-app', provider: 'user-service', pactBrokerUrl: 'https://your-broker.pactflow.io', pactBrokerToken: process.env.PACT_BROKER_TOKEN }); // Provider publishes OAS // pact-broker publish-provider-contract \ // openapi.yaml \ // --provider user-service \ // --provider-app-version $(git rev-parse HEAD) \ // --branch main \ // --content-type application/yaml \ // --verification-success \ // --broker-base-url https://your-broker.pactflow.io \ // --broker-token $PACT_BROKER_TOKEN
Use flexible matching:
javascriptimport { MatchersV3 } from '@pact-foundation/pact'; const { like, // Type matching eachLike, // Array matching regex, // Regex matching integer, // Integer type decimal, // Decimal type boolean, // Boolean type string, // String type datetime, // ISO datetime uuid, // UUID format ipv4Address, // IPv4 address email, // Email format atLeastOneLike, // At least one item matching atMostLike, // At most N items matching constrainedArrayLike // Min/max array } = MatchersV3; const userContract = { id: uuid(), email: email('test@example.com'), name: string('John Doe'), age: integer(25), balance: decimal(100.50), isActive: boolean(true), createdAt: datetime("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"), roles: eachLike('user'), preferences: like({ theme: 'dark', notifications: true }), tags: constrainedArrayLike('tag', 1, 5) };
This skill can leverage the following MCP servers for enhanced capabilities:
| Server | Description | Installation | |--------|-------------|--------------| | PactFlow MCP Server | AI-powered contract testing in IDE | PactFlow Blog |
This skill integrates with the following processes:
contract-testing.js - All phases of contract testingapi-testing.js - API contract validationcontinuous-testing.js - CI/CD contract integrationquality-gates.js - Contract verification gatesWhen executing operations, provide structured output:
json{ "operation": "verify", "provider": "user-service", "providerVersion": "abc123", "consumers": [ { "name": "frontend-app", "version": "def456", "status": "passed", "interactions": 5, "passed": 5, "failed": 0 } ], "canDeploy": true, "environment": "production", "verificationUrl": "https://broker.pactflow.io/verifications/123" }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 6,328 | 5,551 | -12% | 1 | 1 | 0% | 1,342 | 4,366 | +225% | 0 | 0 | — |
case-02 | pass→fail | 10,877 | 12,341 | +13% | 1 | 1 | 0% | 2,303 | 5,927 | +157% | 0 | 0 | — |
case-03 | pass→pass | 16,080 | 15,350 | -5% | 1 | 1 | 0% | 3,032 | 6,408 | +111% | 0 | 0 | — |
case-04 | pass→pass | 16,819 | 14,257 | -15% | 1 | 1 | 0% | 3,365 | 6,388 | +90% | 0 | 0 | — |
case-05 | pass→pass | 4,791 | 4,187 | -13% | 1 | 1 | 0% | 805 | 4,187 | +420% | 0 | 0 | — |
case-06 | pass→pass | 11,177 | 8,826 | -21% | 1 | 1 | 0% | 1,896 | 5,237 | +176% | 0 | 0 | — |
case-07 | pass→pass | 13,160 | 11,977 | -9% | 1 | 1 | 0% | 2,044 | 5,776 | +183% | 0 | 0 | — |
case-08 | pass→pass | 8,555 | 5,288 | -38% | 1 | 1 | 0% | 1,554 | 4,448 | +186% | 0 | 0 | — |
case-09 | pass→pass | 13,208 | 9,647 | -27% | 1 | 1 | 0% | 2,084 | 5,151 | +147% | 0 | 0 | — |
case-10 | pass→pass | 5,509 | 4,063 | -26% | 1 | 1 | 0% | 965 | 4,099 | +325% | 0 | 0 | — |
case-11 | fail→pass | 12,626 | 2,710 | -79% | 1 | 1 | 0% | 1,932 | 3,781 | +96% | 0 | 0 | — |
case-12 | pass→pass | 6,309 | 3,823 | -39% | 1 | 1 | 0% | 1,179 | 4,113 | +249% | 0 | 0 | — |
case-13 | fail→pass | 7,200 | 4,911 | -32% | 1 | 1 | 0% | 1,349 | 4,232 | +214% | 0 | 0 | — |
case-14 | pass→pass | 13,143 | 12,337 | -6% | 1 | 1 | 0% | 2,250 | 5,433 | +141% | 0 | 0 | — |
case-15 | pass→pass | 7,231 | 6,321 | -13% | 1 | 1 | 0% | 1,356 | 4,391 | +224% | 0 | 0 | — |
case-16 | pass→pass | 5,894 | 5,615 | -5% | 1 | 1 | 0% | 1,107 | 4,206 | +280% | 0 | 0 | — |
case-17 | pass→pass | 8,659 | 6,148 | -29% | 1 | 1 | 0% | 1,170 | 4,276 | +265% | 0 | 0 | — |
case-18 | pass→pass | 6,243 | 5,949 | -5% | 1 | 1 | 0% | 940 | 4,258 | +353% | 0 | 0 | — |
case-19 | fail→pass | 7,023 | 4,469 | -36% | 1 | 1 | 0% | 1,291 | 4,201 | +225% | 0 | 0 | — |
case-20 | pass→pass | 10,351 | 7,462 | -28% | 1 | 1 | 0% | 1,540 | 4,780 | +210% | 0 | 0 | — |
case-21 | pass→pass | 11,301 | 8,294 | -27% | 1 | 1 | 0% | 1,861 | 4,796 | +158% | 0 | 0 | — |
case-22 | pass→pass | 6,407 | 6,331 | -1% | 1 | 1 | 0% | 1,190 | 4,269 | +259% | 0 | 0 | — |
case-23 | pass→pass | 8,901 | 7,793 | -12% | 1 | 1 | 0% | 1,237 | 4,715 | +281% | 0 | 0 | — |
case-24 | pass→pass | 14,871 | 12,377 | -17% | 1 | 1 | 0% | 2,253 | 5,844 | +159% | 0 | 0 | — |
case-25 | fail→pass | 13,006 | 4,022 | -69% | 1 | 1 | 0% | 2,096 | 4,032 | +92% | 0 | 0 | — |
case-26 | pass→pass | 13,423 | 13,191 | -2% | 1 | 1 | 0% | 2,367 | 5,960 | +152% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 26 cases were attempted. The headline lift of +15 percentage points is the difference between those two pass rates over the 26 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.