AI-Enhanced Contract Generation and Maintenance
From Manual to Automated Contracts
The biggest barrier to Pact adoption is the effort of writing consumer tests manually. AI eliminates this barrier by analyzing the consumer's actual API client code and generating contracts automatically.
AI-Powered Contract Generation
The Prompt
Analyze this API client code and generate Pact consumer tests for every
external API call it makes.
```typescript
// services/product-client.ts
export class ProductClient {
constructor(private baseUrl: string, private token: string) {}
async getProduct(id: string): Promise<Product> {
const res = await fetch(`${this.baseUrl}/api/v2/products/${id}`, {
headers: { Authorization: `Bearer ${this.token}` }
});
if (!res.ok) throw new ApiError(res.status, await res.text());
return res.json();
}
async searchProducts(query: string, limit = 20): Promise<ProductList> {
const res = await fetch(
`${this.baseUrl}/api/v2/products?q=${encodeURIComponent(query)}&limit=${limit}`,
{ headers: { Authorization: `Bearer ${this.token}` } }
);
if (!res.ok) throw new ApiError(res.status, await res.text());
return res.json();
}
}
Generate Pact consumer tests using @pact-foundation/pact that:
- Define the expected request (method, path, headers, query params)
- Define the expected response shape (using Pact matchers for flexibility)
- Cover both success and error scenarios
- Use Pact matchers (like, eachLike, term) instead of exact values
### What AI Detects from Client Code
| Client Code Pattern | AI-Generated Contract |
|--------------------|-----------------------|
| `fetch(\`/api/v2/products/${id}\`)` | GET /api/v2/products/{id} interaction |
| `headers: { Authorization: \`Bearer ${token}\` }` | Auth header expectation |
| `if (!res.ok) throw new ApiError(...)` | Error scenario interactions |
| `res.json()` | JSON response body expectation |
| `query: string, limit = 20` | Query parameter expectations |
| `Promise<Product>` | Response body shape from Product type |
---
## AI's Role in Contract Maintenance
| Task | Traditional Approach | AI-Augmented Approach |
|------|---------------------|----------------------|
| New consumer feature | Manually write new Pact tests | AI analyzes client code, generates contract |
| Provider schema change | Consumer tests fail in CI | AI detects drift, suggests contract update |
| Contract conflict resolution | Manual negotiation between teams | AI identifies minimal compatible contract |
| Coverage gap detection | Manual audit | AI compares client code paths to Pact interactions |
### Detecting Coverage Gaps
AI can compare the consumer's API client code against existing Pact interactions to find missing coverage:
```python
class ContractCoverageAnalyzer:
def __init__(self, llm, client_code: str, pact_file: str):
self.llm = llm
self.client_code = client_code
self.pact = json.load(open(pact_file))
def find_gaps(self) -> list[str]:
"""Find API calls in client code without Pact coverage."""
prompt = f"""
Compare these two artifacts:
1. API CLIENT CODE (all HTTP calls the consumer makes):
{self.client_code}
2. PACT INTERACTIONS (all API calls covered by contracts):
{json.dumps([i['description'] for i in self.pact['interactions']])}
List any API calls in the client code that do NOT have a
corresponding Pact interaction. For each gap, describe:
- The HTTP method and path
- What the client expects in the response
- Why this gap matters (what could break without a contract)
"""
return self.llm.generate(prompt)
Automatic Contract Updates
When the provider's schema changes, AI can suggest the minimal contract update:
class ContractUpdateAdvisor:
def suggest_update(self, old_schema: dict, new_schema: dict, pact: dict) -> str:
"""Suggest contract updates when provider schema changes."""
prompt = f"""
The provider's schema has changed. Analyze the change and determine
if any Pact contracts need updating.
OLD SCHEMA (relevant section):
{json.dumps(old_schema, indent=2)}
NEW SCHEMA (relevant section):
{json.dumps(new_schema, indent=2)}
CURRENT PACT INTERACTIONS:
{json.dumps(pact['interactions'], indent=2)}
For each affected interaction:
1. Describe what changed
2. Whether the change is backward-compatible
3. If not backward-compatible, suggest the minimal contract update
4. Flag if the consumer code likely needs changes too
"""
return self.llm.generate(prompt)
Contract Testing Anti-Patterns
Anti-Pattern 1: Exact Value Matching
// BAD: contracts that match exact values
.willRespondWith(200, (builder) => {
builder.jsonBody({
id: "550e8400-e29b-41d4-a716-446655440000", // Exact ID
name: "Blue Widget", // Exact name
price: 29.99, // Exact price
});
})
// GOOD: contracts that match structure and type
.willRespondWith(200, (builder) => {
builder.jsonBody({
id: uuid(), // Any valid UUID
name: like("Blue Widget"), // Any string
price: decimal(29.99), // Any decimal number
});
})
Anti-Pattern 2: Testing Provider Logic in Consumer Tests
// BAD: consumer test that validates provider business logic
.executeTest(async (mockServer) => {
const product = await client.getProduct('ABC-123');
expect(product.price).toBeLessThan(100); // Business rule validation
expect(product.name.length).toBeLessThan(200); // Schema constraint
})
// GOOD: consumer test that validates consumer behavior
.executeTest(async (mockServer) => {
const product = await client.getProduct('ABC-123');
expect(product.name).toBeDefined(); // Consumer needs the name field
expect(product.price).toBeDefined(); // Consumer needs the price field
})
Anti-Pattern 3: One Massive Contract
// BAD: one test with every field
.willRespondWith(200, builder => {
builder.jsonBody({
id: uuid(), name: like(""), price: decimal(0),
category: like(""), in_stock: like(true),
created_at: like(""), updated_at: like(""),
description: like(""), images: eachLike(""),
tags: eachLike(""), weight: decimal(0),
dimensions: like({}), shipping_info: like({}),
// ... 30 more fields
});
})
// GOOD: one test per consumer use case, only fields the consumer uses
// Use case 1: product listing (needs id, name, price, image)
// Use case 2: product detail (needs all fields)
// Use case 3: cart display (needs id, name, price, in_stock)
Pact Matchers Quick Reference
| Matcher | Purpose | Example |
|---|---|---|
like(value) |
Match type, not exact value | like("any string") |
eachLike(example) |
Array where each element matches | eachLike({id: uuid()}) |
uuid() |
Valid UUID format | uuid() |
decimal(example) |
Decimal number | decimal(29.99) |
integer(example) |
Integer number | integer(42) |
boolean(example) |
Boolean value | boolean(true) |
term({generate, regex}) |
Match regex pattern | term({generate: "electronics", regex: "electronics|clothing"}) |
datetime(format, example) |
ISO datetime | datetime("yyyy-MM-dd'T'HH:mm:ss", "2026-01-01T00:00:00") |
Key Takeaway
AI transforms contract testing from a manual chore to an automated pipeline. By analyzing client code, AI generates accurate consumer contracts, detects coverage gaps, and suggests updates when schemas change. The key is using Pact matchers for flexibility (not exact values) and testing only what the consumer actually depends on.