Files
english/.opencode/skills/web-testing/references/contract-testing.md
2026-04-12 01:06:31 +07:00

147 lines
3.3 KiB
Markdown

# 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