How to Use Pact for Contract Testing in Microservices
Use
Pact to create consumer-driven contract tests by defining expected interactions in the consumer tests and verifying them against the provider. This ensures both sides agree on the API contract before integration, preventing breaking changes.Syntax
The basic syntax for using Pact involves creating a consumer test that defines expected requests and responses, then running a provider verification to check the provider against the contract.
Key parts include:
pact: Defines the contract between consumer and provider.given: Sets provider state before interaction.uponReceiving: Describes the request the consumer will make.withRequest: Details HTTP method, path, headers, and body.willRespondWith: Defines expected response status, headers, and body.
javascript
const { Pact } = require('@pact-foundation/pact'); const provider = new Pact({ consumer: 'ConsumerService', provider: 'ProviderService', port: 1234 }); provider .setup() .then(() => { return provider.addInteraction({ state: 'provider has data', uponReceiving: 'a request for data', withRequest: { method: 'GET', path: '/data' }, willRespondWith: { status: 200, headers: { 'Content-Type': 'application/json' }, body: { id: 1, name: 'Test' } } }); });
Example
This example shows a simple consumer test using Pact in JavaScript. It defines an expected GET request and response, then verifies the provider against this contract.
javascript
const { Pact } = require('@pact-foundation/pact'); const axios = require('axios'); const provider = new Pact({ consumer: 'MyConsumer', provider: 'MyProvider', port: 1234 }); describe('Pact with MyProvider', () => { beforeAll(() => provider.setup()); afterAll(() => provider.finalize()); beforeEach(() => { return provider.addInteraction({ state: 'user exists', uponReceiving: 'a request for user data', withRequest: { method: 'GET', path: '/user/1' }, willRespondWith: { status: 200, headers: { 'Content-Type': 'application/json' }, body: { id: 1, name: 'Alice' } } }); }); it('fetches user data', async () => { const response = await axios.get('http://localhost:1234/user/1'); expect(response.status).toBe(200); expect(response.data).toEqual({ id: 1, name: 'Alice' }); }); afterEach(() => provider.verify()); });
Output
PASS Pact with MyProvider
โ fetches user data (xx ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Common Pitfalls
- Not running provider verification: Always run
provider.verify()after consumer tests to ensure the provider matches the contract. - Ignoring provider states: Use
stateto set up provider data; missing this can cause test failures. - Hardcoding URLs: Use dynamic ports or environment variables to avoid conflicts.
- Mismatch in request/response details: Ensure headers, body, and status codes exactly match between consumer and provider.
javascript
/* Wrong: Missing provider.verify() call */ // After consumer test // provider.verify() is not called, so provider contract is not checked /* Right: Include provider.verify() */ afterEach(() => provider.verify());
Quick Reference
| Concept | Description |
|---|---|
| Pact | Library to create and verify contracts between consumer and provider |
| Consumer Test | Defines expected requests and responses from the consumer side |
| Provider Verification | Checks the provider service against the saved contract |
| State | Sets provider data or conditions before interaction |
| Interaction | Defines one request and expected response pair |
Key Takeaways
Define expected interactions in consumer tests using Pact to create contracts.
Always run provider verification to ensure the provider meets the contract.
Use provider states to prepare data and avoid test failures.
Match request and response details exactly between consumer and provider.
Avoid hardcoding ports and URLs; use configuration for flexibility.