@clevercloud/doublure
v0.0.2
Published
Mock API testing framework with a Fastify-based mock HTTP server, fluent mock configuration, call verification, and SSE support. Integrates with Vitest and Web Test Runner for identical usage in Node.js and browser environments.
Maintainers
Keywords
Readme
Mock API Testing Infrastructure
A comprehensive testing framework for mocking HTTP APIs in both Node.js and browser environments. This infrastructure provides a complete solution for creating, managing, and verifying mock API responses during testing.
Overview
The Mock API system consists of several key components:
- Mock Server - HTTP server that responds to API calls with configured responses
- Admin Interface - Management API for configuring mocks and retrieving call logs
- Fluent API - Chainable interface for easy mock configuration
- Verification System - Tools for validating API calls and responses
- Test Integration - Hooks and plugins for seamless test framework integration
Quick Start
import { mockScenario } from '@clevercloud/doublure';
import { serverStart } from '@clevercloud/doublure/server';
// Start mock server
const { mockClient, stop } = await serverStart();
try {
// Configure a mock
await mockScenario(mockClient)
.when({ method: 'GET', path: '/api/users' })
.respond({ status: 200, body: [{ id: 1, name: 'John' }] });
// Make API calls
const response = await fetch(`${mockClient.baseUrl}/api/users`);
const users = await response.json();
// Verify calls
const calls = await mockClient.getCalls();
expect(calls).toHaveLength(1);
} finally {
await stop();
}Core Components
MockClient (lib/mock-client.ts)
The foundational client for interacting with the mock server infrastructure.
Key Features:
- Configures mock responses via admin interface
- Retrieves call logs for verification
- Resets mock state between tests
- Works in both Node.js and browser environments
Example:
import { MockClient } from '@clevercloud/doublure';
const mockClient = new MockClient('http://localhost:3001', 'http://localhost:3000');
// Configure a regular HTTP mock
await mockClient.mock({
request: { method: 'GET', path: '/api/users' },
response: { status: 200, body: [] },
});
// Configure a Server-Sent Events mock
await mockClient.mock({
request: { method: 'GET', path: '/api/events' },
response: {
status: 200,
events: [
{ type: 'message', event: 'DATA', data: 'hello world' },
{ type: 'message', event: 'END_OF_STREAM' },
],
delayBetween: 50,
},
});
// Get call logs
const calls = await mockClient.getCalls();MockScenario (lib/mock-scenario.ts)
Fluent API for describing a mocked test scenario: a set of request/response pairs, optionally composed with a callback to exercise them and expectations to verify the calls that were made.
Key Features:
- Chainable API for mock configuration
- Single or multiple mocks per scenario
- Execute a callback after mocks are applied
- Verify API calls with detailed inspection
import { mockScenario } from '@clevercloud/doublure';
// Mock-only scenario
await mockScenario(mockClient)
.when({ method: 'GET', path: '/api/users' })
.respond({ status: 200, body: [] })
.when({ method: 'POST', path: '/api/users' })
.respond({ status: 201, body: { id: 1 } })
.when({ method: 'GET', path: '/api/events' })
.respond({
status: 200,
events: [
{ type: 'message', event: 'DATA', data: 'hello world' },
{ type: 'close' }, // ask the server to close the SSE
],
delayBetween: 50,
});
// HTTP response verification
const result = await mockScenario(mockClient)
.when({ method: 'GET', path: '/api/users' })
.respond({ status: 200, body: [] })
.thenCall(async () => {
return await fetch('/api/users').then((r) => r.json());
})
.verify((calls) => {
expect(calls.count).toBe(1);
expect(calls.first.method).toBe('GET');
});
// Server-Sent Events verification
await mockScenario(mockClient)
.when({ method: 'GET', path: '/api/stream' })
.respond({
status: 200,
events: [
{ type: 'message', event: 'DATA', data: 'event1' },
{ type: 'message', event: 'DATA', data: 'event2' },
{ type: 'close' },
],
delayBetween: 10,
})
.thenCall(async () => {
// Your SSE client code here - could use EventSource in browser
// or a streaming HTTP client in Node.js to read the events
await fetch('/api/stream', {
headers: { Accept: 'text/event-stream' },
});
})
.verify((calls) => {
expect(calls.count).toBe(1);
expect(calls.first.headers.accept).toBe('text/event-stream');
});Mock Server (server.ts)
HTTP server implementation that handles both mock responses and admin operations.
Key Features:
- Serves mock responses based on configured rules
- Provides admin API for mock management
- Logs all incoming requests for verification
- Supports response delays and throttling
- Server-Sent Events (SSE) streaming with configurable events and delays
Server Endpoints:
POST /mock- Configure a new mock responsePOST /calls- Retrieve call logs for specific requestsPOST /reset- Clear all mocks and call logs*- Serve configured mock responses
SSE Event Types:
{ type: 'message', event: 'EVENT_NAME', data?: any, id?: string, retry?: number }- Send SSE message{ type: 'close' }- Close the connection
Test Integration
Test Hooks (support/hooks.ts)
Lifecycle hooks for integrating with test frameworks like Mocha, Jest, etc.
Example with Mocha:
import { doublureHooks } from '@clevercloud/doublure/testing';
describe('API Tests', () => {
const hooks = doublureHooks();
let newScenario;
before(async () => {
newScenario = await hooks.before();
});
beforeEach(hooks.beforeEach);
after(hooks.after);
it('should handle user requests', async () => {
const result = await newScenario()
.when({ method: 'GET', path: '/api/users' })
.respond({ status: 200, body: [] })
.thenCall(() => fetch('/api/users').then((r) => r.json()))
.verify((calls) => {
expect(calls.count).toBe(1);
expect(calls.first.method).toBe('GET');
});
// Your test code here
});
it('should handle SSE streams', async () => {
await newScenario()
.when({ method: 'GET', path: '/api/events' })
.respond({
status: 200,
events: [
{ type: 'message', event: 'DATA', data: 'event1' },
{ type: 'message', event: 'END_OF_STREAM' },
],
delayBetween: 10,
});
// Your SSE client code here - use EventSource in browser or streaming client in Node.js
const response = await fetch('/api/events', {
headers: { Accept: 'text/event-stream' },
});
expect(response.headers.get('content-type')).toBe('text/event-stream');
});
});Vitest Plugin (support/vitest-plugin.ts)
Plugin for integrating with Vitest browser mode for browser-based testing.
Configuration:
// vitest.config.ts
import { vitestPlugin } from '@clevercloud/doublure/vitest';
import { playwright } from '@vitest/browser-playwright';
import { createConfigBuilder } from 'vitest/config';
export default createConfigBuilder({
plugins: [vitestPlugin()],
test: {
include: ['src/**/*.test.ts'],
browser: {
enabled: true,
headless: true,
provider: playwright(),
instances: [{ browser: 'chromium' }],
},
},
});Web Test Runner Plugin (support/web-test-runner-plugin.ts)
Plugin for integrating with Web Test Runner for browser-based testing.
Configuration:
// web-test-runner.config.js
import { mockApiPlugin } from '@clevercloud/doublure/wtr';
export default {
plugins: [
mockApiPlugin,
// other plugins...
],
};Cross-Environment Testing:
The real power of this mock API system is environment transparency. When you combine a browser plugin (Vitest or Web Test Runner) with the test hooks, your test code remains identical whether running in Node.js or the browser.
// This EXACT same test code works in both Node.js and browser:
import { doublureHooks } from '@clevercloud/doublure/testing';
describe('API Tests', () => {
const hooks = doublureHooks();
let newScenario;
before(async () => {
newScenario = await hooks.before(); // Automatically detects environment
});
beforeEach(hooks.beforeEach);
after(hooks.after);
it('works everywhere', async () => {
// Same code, different environments!
const users = await newScenario()
.when({ method: 'GET', path: '/api/users' })
.respond({ status: 200, body: [{ id: 1, name: 'John' }] })
.thenCall(() => fetch('/api/users').then((r) => r.json()));
expect(users).toHaveLength(1);
});
});Behind the scenes:
- Node.js: Starts real HTTP servers on free ports
- Browser: Connects to proxy routes provided by the Vitest or Web Test Runner plugin
- Your code: Stays exactly the same!
This eliminates the need for environment-specific test code and ensures your tests behave consistently across all environments.
