init
This commit is contained in:
146
.opencode/skills/web-testing/references/contract-testing.md
Normal file
146
.opencode/skills/web-testing/references/contract-testing.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# Contract Testing
|
||||
|
||||
## When to Use
|
||||
|
||||
- Microservices communicating via HTTP/REST
|
||||
- Frontend consuming backend APIs
|
||||
- Multiple teams working on separate services
|
||||
- Preventing integration failures at runtime
|
||||
|
||||
## Pact (Consumer-Driven Contracts)
|
||||
|
||||
### Consumer Side
|
||||
|
||||
```typescript
|
||||
import { Pact } from '@pact-foundation/pact';
|
||||
|
||||
const provider = new Pact({
|
||||
consumer: 'Frontend',
|
||||
provider: 'UserService',
|
||||
});
|
||||
|
||||
describe('User API', () => {
|
||||
beforeAll(() => provider.setup());
|
||||
afterEach(() => provider.verify());
|
||||
afterAll(() => provider.finalize());
|
||||
|
||||
it('gets user by id', async () => {
|
||||
await provider.addInteraction({
|
||||
state: 'user 123 exists',
|
||||
uponReceiving: 'request for user 123',
|
||||
withRequest: {
|
||||
method: 'GET',
|
||||
path: '/users/123',
|
||||
},
|
||||
willRespondWith: {
|
||||
status: 200,
|
||||
body: {
|
||||
id: '123',
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const user = await userClient.getUser('123');
|
||||
expect(user.name).toBe('John Doe');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Provider Side
|
||||
|
||||
```typescript
|
||||
import { Verifier } from '@pact-foundation/pact';
|
||||
|
||||
describe('Pact Verification', () => {
|
||||
it('validates consumer expectations', async () => {
|
||||
await new Verifier({
|
||||
providerBaseUrl: 'http://localhost:3000',
|
||||
pactBrokerUrl: process.env.PACT_BROKER_URL,
|
||||
provider: 'UserService',
|
||||
providerVersion: process.env.GIT_SHA,
|
||||
stateHandlers: {
|
||||
'user 123 exists': async () => {
|
||||
await db.users.insert({ id: '123', name: 'John Doe' });
|
||||
},
|
||||
},
|
||||
}).verifyProvider();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## MSW (Mock Service Worker)
|
||||
|
||||
### Setup
|
||||
|
||||
```typescript
|
||||
import { setupServer } from 'msw/node';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
|
||||
const handlers = [
|
||||
http.get('/api/users/:id', ({ params }) => {
|
||||
return HttpResponse.json({
|
||||
id: params.id,
|
||||
name: 'John Doe',
|
||||
});
|
||||
}),
|
||||
];
|
||||
|
||||
export const server = setupServer(...handlers);
|
||||
|
||||
// In test setup
|
||||
beforeAll(() => server.listen());
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterAll(() => server.close());
|
||||
```
|
||||
|
||||
### Per-Test Override
|
||||
|
||||
```typescript
|
||||
it('handles server error', async () => {
|
||||
server.use(
|
||||
http.get('/api/users/:id', () => {
|
||||
return HttpResponse.json({ error: 'Not found' }, { status: 404 });
|
||||
})
|
||||
);
|
||||
|
||||
await expect(userClient.getUser('123')).rejects.toThrow();
|
||||
});
|
||||
```
|
||||
|
||||
## Pact + MSW Combined
|
||||
|
||||
```typescript
|
||||
// Use MSW to simulate provider during Pact consumer tests
|
||||
const pactMswHandler = http.get('/api/users/:id', () => {
|
||||
return HttpResponse.json(expectedPactResponse);
|
||||
});
|
||||
```
|
||||
|
||||
## CI Integration
|
||||
|
||||
```yaml
|
||||
# Consumer publishes contract
|
||||
- run: npx pact-broker publish ./pacts
|
||||
--consumer-app-version=${{ github.sha }}
|
||||
--broker-base-url=${{ secrets.PACT_BROKER_URL }}
|
||||
|
||||
# Provider verifies
|
||||
- run: npm run test:pact:verify
|
||||
--provider-app-version=${{ github.sha }}
|
||||
|
||||
# Can-I-Deploy check
|
||||
- run: npx pact-broker can-i-deploy
|
||||
--pacticipant=Frontend
|
||||
--version=${{ github.sha }}
|
||||
--to-environment=production
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Consumer-first:** Consumers define expectations
|
||||
- **Version contracts:** Tie to git SHA
|
||||
- **Pact Broker:** Central contract management
|
||||
- **can-i-deploy:** Gate deployments on contract verification
|
||||
- **State handlers:** Prepare provider data for each scenario
|
||||
Reference in New Issue
Block a user