Creating Mock Servers
Mock servers are the foundation of consumer testing in Entente. They’re created from provider OpenAPI specifications and can serve both dynamic responses and deterministic fixture-based responses.
Mock Server Creation
Section titled “Mock Server Creation”Basic Mock Server
Section titled “Basic Mock Server”Create a mock server from a provider’s OpenAPI specification:
import { createClient } from '@entente/consumer'
const client = createClient({ serviceUrl: 'https://entente.company.com', apiKey: process.env.ENTENTE_API_KEY, consumer: 'castle-client', environment: 'test'})
const mock = await client.createMock('castle-service', '0.1.0')
console.log(`Mock server URL: ${mock.url}`) // http://localhost:45623console.log(`Mock server port: ${mock.port}`) // 45623
// Use in your testsconst response = await fetch(`${mock.url}/castles`)const castles = await response.json()
// Always close when doneawait mock.close()Advanced Configuration
Section titled “Advanced Configuration”Configure mock server behavior with detailed options:
const mock = await client.createMock('castle-service', '0.1.0', { // Use fixtures for deterministic responses useFixtures: true,
// Provide local fixtures for offline development localFixtures: loadLocalFixtures(),
// Validate requests against OpenAPI spec validateRequests: true,
// Validate responses against OpenAPI spec validateResponses: true,
// Use specific port (optional, random port if not specified) port: 3001,
// Git branch for spec lookup (defaults to 'main') branch: 'feature/new-api'})Mock Server Modes
Section titled “Mock Server Modes”Fixture-Based Mode (Deterministic)
Section titled “Fixture-Based Mode (Deterministic)”When fixtures are available, mock servers return deterministic responses:
const mock = await client.createMock('castle-service', '0.1.0', { useFixtures: true, localFixtures: [ { id: 'fixture_1', operation: 'listCastles', data: { request: { method: 'GET', path: '/castles' }, response: { status: 200, body: [ { id: '1', name: 'Versailles', region: 'Île-de-France' }, { id: '2', name: 'Fontainebleau', region: 'Île-de-France' } ] } } } ]})
// Always returns the same castles from fixturesconst castles = await fetch(`${mock.url}/castles`).then(r => r.json())console.log(castles.length) // Always 2Dynamic Mode (Schema-Based)
Section titled “Dynamic Mode (Schema-Based)”Without fixtures, mock servers generate responses based on OpenAPI schemas:
const mock = await client.createMock('castle-service', '0.1.0', { useFixtures: false})
// Returns randomly generated data matching the schemaconst castles = await fetch(`${mock.url}/castles`).then(r => r.json())console.log(castles) // Different data each time, but matches schemaRequest/Response Validation
Section titled “Request/Response Validation”Request Validation
Section titled “Request Validation”Enable request validation to catch client-side errors:
const mock = await client.createMock('castle-service', '0.1.0', { validateRequests: true})
// This will fail validation and return 400:await fetch(`${mock.url}/castles`, { method: 'POST', headers: { 'Content-Type': 'text/plain' }, // ❌ Spec requires application/json body: 'invalid json'})Common Request Validation Errors:
- Wrong content type
- Missing required headers
- Invalid request body structure
- Missing required fields
- Invalid field types
Response Validation
Section titled “Response Validation”Enable response validation to catch provider spec issues:
const mock = await client.createMock('castle-service', '0.1.0', { validateResponses: true})
// If fixtures or generated responses don't match the spec,// the mock server will return a validation errorCommon Response Validation Errors:
- Fixture responses don’t match OpenAPI schema
- Generated examples in spec are invalid
- Missing required response fields
- Wrong response content types
Fixture Management
Section titled “Fixture Management”Local Fixtures
Section titled “Local Fixtures”Provide fixtures for offline development and deterministic testing:
import { readFileSync } from 'node:fs'import { join } from 'node:path'
const loadLocalFixtures = () => { try { const fixturesPath = join(process.cwd(), 'fixtures', 'castle-service.json') return JSON.parse(readFileSync(fixturesPath, 'utf-8')) } catch (error) { console.warn('No local fixtures found, using server fixtures') return [] }}
const mock = await client.createMock('castle-service', '0.1.0', { useFixtures: true, localFixtures: loadLocalFixtures()})Fixture Priority
Section titled “Fixture Priority”When both local and server fixtures are available, Entente prioritizes them:
- Local fixtures - Highest priority, used for specific test scenarios
- Approved server fixtures - Provider-verified responses
- Draft server fixtures - Pending approval, used as fallback
- Dynamic responses - Generated from OpenAPI schema
const mock = await client.createMock('castle-service', '0.1.0', { useFixtures: true, localFixtures: localFixtures, // Priority 1 // Server fixtures automatically loaded: // - Approved fixtures (Priority 2) // - Draft fixtures (Priority 3) // - Dynamic generation (Priority 4)})Fixture Matching
Section titled “Fixture Matching”Fixtures are matched based on:
- HTTP Method - Must match exactly (GET, POST, etc.)
- Path Pattern - Exact match or parameterized match
- Request Body - For POST/PUT requests (if specified in fixture)
// Fixture for GET /castles/{id}{ "data": { "request": { "method": "GET", "path": "/castles/550e8400-e29b-41d4-a716-446655440000" }, "response": { "status": 200, "body": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Versailles" } } }}
// Matches these requests:// GET /castles/550e8400-e29b-41d4-a716-446655440000 ✅// GET /castles/abc123 ✅ (parameter matching)// GET /castles ❌ (different path)// POST /castles/550e8400-e29b-41d4-a716-446655440000 ❌ (different method)Error Simulation
Section titled “Error Simulation”Fixture-Based Errors
Section titled “Fixture-Based Errors”Include error responses in fixtures for comprehensive testing:
const errorFixtures = [ { id: 'castle_not_found', operation: 'getCastle', data: { request: { method: 'GET', path: '/castles/nonexistent' }, response: { status: 404, headers: { 'content-type': 'application/json' }, body: { error: 'not_found', message: 'Castle not found' } } } }, { id: 'validation_error', operation: 'createCastle', data: { request: { method: 'POST', path: '/castles', body: { name: '', region: 'Test', yearBuilt: 999 } }, response: { status: 400, headers: { 'content-type': 'application/json' }, body: { error: 'validation_error', message: 'Name cannot be empty and year must be >= 1000' } } } }]
const mock = await client.createMock('castle-service', '0.1.0', { useFixtures: true, localFixtures: errorFixtures})
// Test error handlingawait expect( fetch(`${mock.url}/castles/nonexistent`)).rejects.toThrow('Castle not found')Dynamic Error Responses
Section titled “Dynamic Error Responses”Without specific error fixtures, mock servers return errors defined in the OpenAPI spec:
{ "paths": { "/castles/{id}": { "get": { "responses": { "200": { "description": "Castle found" }, "404": { "description": "Castle not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "error": "not_found", "message": "Castle not found" } } } } } } } }}Mock Server Lifecycle
Section titled “Mock Server Lifecycle”Setup and Teardown
Section titled “Setup and Teardown”Properly manage mock server lifecycle in tests:
describe('Castle API Tests', () => { let client: ReturnType<typeof createClient> let mock: Awaited<ReturnType<typeof client.createMock>> let castleApi: CastleApiClient
beforeAll(async () => { client = createClient({ serviceUrl: process.env.ENTENTE_SERVICE_URL, apiKey: process.env.ENTENTE_API_KEY, consumer: 'castle-client', environment: 'test' })
mock = await client.createMock('castle-service', '0.1.0', { useFixtures: true, localFixtures: loadLocalFixtures() })
castleApi = new CastleApiClient(mock.url) })
afterAll(async () => { if (mock) { await mock.close() // Important: uploads recorded interactions } })
// Your tests here...})Per-Test Mock Servers
Section titled “Per-Test Mock Servers”For isolation, create separate mock servers per test:
describe('Castle API Tests', () => { let client: ReturnType<typeof createClient>
beforeAll(async () => { client = createClient({ serviceUrl: process.env.ENTENTE_SERVICE_URL, apiKey: process.env.ENTENTE_API_KEY, consumer: 'castle-client', environment: 'test' }) })
it('should list castles', async () => { const mock = await client.createMock('castle-service', '0.1.0', { useFixtures: true, localFixtures: listCastlesFixtures })
try { const castleApi = new CastleApiClient(mock.url) const castles = await castleApi.getAllCastles() expect(castles).toHaveLength(2) } finally { await mock.close() } })
it('should create castle', async () => { const mock = await client.createMock('castle-service', '0.1.0', { useFixtures: true, localFixtures: createCastleFixtures })
try { const castleApi = new CastleApiClient(mock.url) const castle = await castleApi.createCastle({ name: 'Test Castle', region: 'Test Region', yearBuilt: 1500 }) expect(castle).toHaveProperty('id') } finally { await mock.close() } })})Provider Version Management
Section titled “Provider Version Management”Version Selection
Section titled “Version Selection”Specify which provider version to mock:
// Use specific versionconst mock = await client.createMock('castle-service', '0.1.0')
// Use latest version (may change over time)const mock = await client.createMock('castle-service', 'latest')
// Use version from specific branchconst mock = await client.createMock('castle-service', '0.1.0', { branch: 'feature/new-endpoints'})Version Compatibility
Section titled “Version Compatibility”Entente handles version compatibility automatically:
// Request: castle-service v0.1.0const mock = await client.createMock('castle-service', '0.1.0')
// If v0.1.0 not deployed, Entente may use v0.1.2 and log:// "📋 Using latest provider version: 0.1.2 for castle-service"// "ℹ️ Using spec for castle-service@0.1.2 (not currently deployed in test)"Debugging Mock Servers
Section titled “Debugging Mock Servers”Logging and Debugging
Section titled “Logging and Debugging”Enable detailed logging to debug mock server behavior:
const mock = await client.createMock('castle-service', '0.1.0', { useFixtures: true, localFixtures: fixtures})
// Monitor requests and responsesmock.onRequest((request, response) => { console.log(`📥 ${request.method} ${request.path}`) console.log(`📤 ${response.status} (${response.duration}ms)`)})Fixture Inspection
Section titled “Fixture Inspection”Check which fixtures are loaded:
const mock = await client.createMock('castle-service', '0.1.0', { useFixtures: true, localFixtures: fixtures})
const loadedFixtures = mock.getFixtures()console.log(`Loaded ${loadedFixtures.length} fixtures:`)loadedFixtures.forEach(fixture => { console.log(`- ${fixture.operation}: ${fixture.status}`)})Network Inspection
Section titled “Network Inspection”Use browser dev tools or network debugging tools to inspect requests:
// Mock server runs on localhost, visible in browser dev toolsconst mock = await client.createMock('castle-service', '0.1.0')console.log(`Debug at: ${mock.url}`)
// You can also make requests directly:const response = await fetch(`${mock.url}/castles`)console.log('Response headers:', Object.fromEntries(response.headers))Performance Considerations
Section titled “Performance Considerations”Mock Server Startup Time
Section titled “Mock Server Startup Time”Mock servers using Prism have some startup overhead:
// Faster: Reuse mock server across testslet globalMock: EntenteMock
beforeAll(async () => { globalMock = await client.createMock('castle-service', '0.1.0', { useFixtures: true, localFixtures: allFixtures })})
// Slower: Create new mock server per testbeforeEach(async () => { const mock = await client.createMock('castle-service', '0.1.0') // ...})Fixture Loading
Section titled “Fixture Loading”Local fixtures load faster than server fixtures:
// Faster: Local fixturesconst mock = await client.createMock('castle-service', '0.1.0', { useFixtures: true, localFixtures: require('./fixtures/castle-service.json')})
// Slower: Server fixtures (requires API call)const mock = await client.createMock('castle-service', '0.1.0', { useFixtures: true // No localFixtures - will fetch from server})Next Steps
Section titled “Next Steps”- Managing Fixtures - Learn fixture management and approval workflows
- Recording Interactions - Understand how interactions are captured
- GitHub Actions - Integrate consumer testing into CI/CD
Mock servers provide the foundation for reliable consumer testing. Use fixtures for deterministic tests and enable recording in CI to capture real usage patterns that providers can verify against.