Mastering API Mocking for Efficient Software Testing
API mocking is a critical technique in modern software development that enables teams to develop, test, and debug applications without depending on external services or incomplete backend implementations. This comprehensive guide covers everything you need to know about API mocking strategies, tools, and best practices.
Understanding API Mocking
API mocking involves creating simulated versions of APIs that behave like real services but are controlled, predictable, and independent of external dependencies. This technique is essential for parallel development, comprehensive testing, and reliable CI/CD pipelines.
Why API Mocking is Essential
Development Independence: Frontend and backend teams can work in parallel Testing Reliability: Eliminate external service dependencies in tests Performance Control: Simulate various response times and conditions Cost Efficiency: Reduce API calls to expensive third-party services Environment Consistency: Maintain identical behavior across dev, test, and staging
Types of API Mocking
Static Mocks: Fixed responses defined at setup time Dynamic Mocks: Responses generated based on request parameters Smart Mocks: Context-aware responses that maintain state Proxy Mocks: Selective forwarding to real or mock services Record-and-Replay: Captured real responses for later use
API Mocking Strategies
1. Contract-First Development
Define API contracts before implementation to enable parallel development:
# OpenAPI specification for user service
openapi: 3.0.0
info:
title: User Service API
version: 1.0.0
paths:
/users:
get:
summary: List users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 10
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
users:
type: array
items:
$ref: '#/components/schemas/User'
pagination:
$ref: '#/components/schemas/Pagination'
components:
schemas:
User:
type: object
properties:
id:
type: string
format: uuid
firstName:
type: string
lastName:
type: string
email:
type: string
format: email
createdAt:
type: string
format: date-timeGenerate API specifications and mock data with our API mocking tools.
2. Response Template System
Create reusable response templates for different scenarios:
class ResponseTemplateSystem {
constructor() {
this.templates = new Map();
this.scenarios = new Map();
}
addTemplate(name, template) {
this.templates.set(name, template);
}
addScenario(name, scenarioConfig) {
this.scenarios.set(name, scenarioConfig);
}
generateResponse(templateName, scenario = 'default', context = {}) {
const template = this.templates.get(templateName);
const scenarioConfig = this.scenarios.get(scenario) || {};
if (!template) {
throw new Error(Template '${templateName}' not found);
}
return this.processTemplate(template, scenarioConfig, context);
}
processTemplate(template, scenario, context) {
// Clone template to avoid mutations
const response = JSON.parse(JSON.stringify(template));
// Apply scenario modifications
if (scenario.statusCode) {
response.statusCode = scenario.statusCode;
}
if (scenario.delay) {
response.delay = scenario.delay;
}
// Generate dynamic data
if (response.body && typeof response.body === 'object') {
response.body = this.generateDynamicData(response.body, context);
}
return response;
}
generateDynamicData(data, context) {
if (Array.isArray(data)) {
return data.map(item => this.generateDynamicData(item, context));
}
if (typeof data === 'object' && data !== null) {
const result = {};
for (const [key, value] of Object.entries(data)) {
if (typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}')) {
// Dynamic data generation
const generator = value.slice(2, -2).trim();
result[key] = this.executeGenerator(generator, context);
} else if (typeof value === 'object') {
result[key] = this.generateDynamicData(value, context);
} else {
result[key] = value;
}
}
return result;
}
return data;
}
executeGenerator(generator, context) {
const { faker } = require('@faker-js/faker');
switch (generator) {
case 'uuid':
return faker.string.uuid();
case 'firstName':
return faker.person.firstName();
case 'lastName':
return faker.person.lastName();
case 'email':
return faker.internet.email();
case 'phoneNumber':
return faker.phone.number();
case 'company':
return faker.company.name();
case 'dateRecent':
return faker.date.recent().toISOString();
case 'futureDate':
return faker.date.future().toISOString();
case 'randomNumber':
return faker.number.int({ min: 1, max: 1000 });
case 'address':
return {
street: faker.location.streetAddress(),
city: faker.location.city(),
state: faker.location.state(),
zipCode: faker.location.zipCode(),
country: faker.location.country()
};
default:
// Check if context provides the value
return context[generator] || [Unknown generator: ${generator}];
}
}
}// Usage example
const templateSystem = new ResponseTemplateSystem();
// Define user list template
templateSystem.addTemplate('userList', {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
users: [
{
id: '{{uuid}}',
firstName: '{{firstName}}',
lastName: '{{lastName}}',
email: '{{email}}',
phone: '{{phoneNumber}}',
createdAt: '{{dateRecent}}',
profile: {
company: '{{company}}',
address: '{{address}}'
}
}
],
pagination: {
page: 1,
limit: 10,
total: '{{randomNumber}}',
hasMore: true
}
}
});
// Define scenarios
templateSystem.addScenario('success', { statusCode: 200, delay: 100 });
templateSystem.addScenario('slow', { statusCode: 200, delay: 3000 });
templateSystem.addScenario('error', { statusCode: 500, delay: 100 });
templateSystem.addScenario('notFound', { statusCode: 404, delay: 50 });
// Generate responses
console.log(templateSystem.generateResponse('userList', 'success'));
console.log(templateSystem.generateResponse('userList', 'slow'));
3. State Management in Mocks
Implement stateful mocks that remember previous interactions:
class StatefulApiMock {
constructor() {
this.state = {
users: new Map(),
sessions: new Map(),
counters: new Map()
};
this.routes = new Map();
this.middleware = [];
}
use(middleware) {
this.middleware.push(middleware);
}
addRoute(method, path, handler) {
const key = ${method.toUpperCase()}:${path};
this.routes.set(key, handler);
}
async handleRequest(req) {
// Apply middleware
for (const middleware of this.middleware) {
req = await middleware(req, this.state);
}
// Find matching route
const key = ${req.method}:${req.path};
const handler = this.routes.get(key);
if (!handler) {
return {
statusCode: 404,
body: { error: 'Route not found' }
};
}
// Execute handler with state access
return await handler(req, this.state);
}
// User management routes
setupUserRoutes() {
// GET /users - List users
this.addRoute('GET', '/users', async (req, state) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const offset = (page - 1) * limit;
const allUsers = Array.from(state.users.values());
const paginatedUsers = allUsers.slice(offset, offset + limit);
return {
statusCode: 200,
body: {
users: paginatedUsers,
pagination: {
page: page,
limit: limit,
total: allUsers.length,
pages: Math.ceil(allUsers.length / limit)
}
}
};
});
// POST /users - Create user
this.addRoute('POST', '/users', async (req, state) => {
const userData = req.body;
const userId = faker.string.uuid();
const user = {
id: userId,
...userData,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
state.users.set(userId, user);
return {
statusCode: 201,
body: user
};
});
// GET /users/:id - Get user by ID
this.addRoute('GET', '/users/:id', async (req, state) => {
const userId = req.params.id;
const user = state.users.get(userId);
if (!user) {
return {
statusCode: 404,
body: { error: 'User not found' }
};
}
return {
statusCode: 200,
body: user
};
});
// PUT /users/:id - Update user
this.addRoute('PUT', '/users/:id', async (req, state) => {
const userId = req.params.id;
const updates = req.body;
const existingUser = state.users.get(userId);
if (!existingUser) {
return {
statusCode: 404,
body: { error: 'User not found' }
};
}
const updatedUser = {
...existingUser,
...updates,
updatedAt: new Date().toISOString()
};
state.users.set(userId, updatedUser);
return {
statusCode: 200,
body: updatedUser
};
});
// DELETE /users/:id - Delete user
this.addRoute('DELETE', '/users/:id', async (req, state) => {
const userId = req.params.id;
if (!state.users.has(userId)) {
return {
statusCode: 404,
body: { error: 'User not found' }
};
}
state.users.delete(userId);
return {
statusCode: 204,
body: null
};
});
}
// Seed initial data
seedData() {
// Create some initial users
for (let i = 0; i < 25; i++) {
const userId = faker.string.uuid();
const user = {
id: userId,
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
email: faker.internet.email(),
phone: faker.phone.number(),
company: faker.company.name(),
address: {
street: faker.location.streetAddress(),
city: faker.location.city(),
state: faker.location.state(),
zipCode: faker.location.zipCode(),
country: faker.location.country()
},
createdAt: faker.date.past({ years: 2 }).toISOString(),
updatedAt: faker.date.recent().toISOString()
};
this.state.users.set(userId, user);
}
}
}// Usage
const apiMock = new StatefulApiMock();
// Add logging middleware
apiMock.use(async (req, state) => {
console.log(${req.method} ${req.path}, req.query, req.body);
return req;
});
// Add authentication middleware
apiMock.use(async (req, state) => {
if (req.path.startsWith('/users') && req.method !== 'GET') {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new Error('Authentication required');
}
}
return req;
});
// Setup routes and seed data
apiMock.setupUserRoutes();
apiMock.seedData();
// Example requests
const exampleRequests = [
{ method: 'GET', path: '/users', query: { page: 1, limit: 5 } },
{ method: 'POST', path: '/users', body: { firstName: 'John', lastName: 'Doe', email: 'john@example.com' } },
{ method: 'GET', path: '/users/some-id' }
];
Mock Server Implementation
1. Express-Based Mock Server
Create a full-featured mock server using Express.js:
const express = require('express');
const cors = require('cors');
const { faker } = require('@faker-js/faker');class MockApiServer {
constructor(port = 3001) {
this.app = express();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
setupMiddleware() {
this.app.use(cors());
this.app.use(express.json());
// Request logging
this.app.use((req, res, next) => {
console.log(${new Date().toISOString()} - ${req.method} ${req.path});
next();
});
// Response delay simulation
this.app.use((req, res, next) => {
const delay = req.query.delay ? parseInt(req.query.delay) : 0;
if (delay > 0) {
setTimeout(next, delay);
} else {
next();
}
});
// Error simulation
this.app.use((req, res, next) => {
const errorRate = parseFloat(req.query.errorRate) || 0;
if (Math.random() < errorRate) {
return res.status(500).json({
error: 'Simulated server error',
timestamp: new Date().toISOString()
});
}
next();
});
}
setupRoutes() {
// Health check
this.app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// Dynamic user generation
this.app.get('/users', (req, res) => {
const count = parseInt(req.query.count) || 10;
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const users = Array.from({ length: count }, (_, index) => ({
id: faker.string.uuid(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
email: faker.internet.email(),
phone: faker.phone.number(),
avatar: faker.image.avatar(),
address: {
street: faker.location.streetAddress(),
city: faker.location.city(),
state: faker.location.state(),
zipCode: faker.location.zipCode(),
country: faker.location.country()
},
company: {
name: faker.company.name(),
department: faker.commerce.department(),
jobTitle: faker.person.jobTitle()
},
createdAt: faker.date.past({ years: 2 }).toISOString(),
isActive: faker.datatype.boolean()
}));
// Pagination
const startIndex = (page - 1) * limit;
const endIndex = startIndex + limit;
const paginatedUsers = users.slice(startIndex, endIndex);
res.json({
users: paginatedUsers,
pagination: {
page: page,
limit: limit,
total: users.length,
pages: Math.ceil(users.length / limit),
hasNext: endIndex < users.length,
hasPrev: page > 1
}
});
});
// Individual user
this.app.get('/users/:id', (req, res) => {
const userId = req.params.id;
// Simulate user not found
if (Math.random() < 0.1) {
return res.status(404).json({
error: 'User not found',
userId: userId
});
}
const user = {
id: userId,
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
email: faker.internet.email(),
phone: faker.phone.number(),
avatar: faker.image.avatar(),
bio: faker.lorem.paragraph(),
preferences: {
theme: faker.helpers.arrayElement(['light', 'dark']),
notifications: faker.datatype.boolean(),
language: faker.helpers.arrayElement(['en', 'es', 'fr', 'de'])
},
stats: {
loginCount: faker.number.int({ min: 0, max: 500 }),
lastLogin: faker.date.recent().toISOString(),
accountCreated: faker.date.past({ years: 3 }).toISOString()
}
};
res.json(user);
});
// Create user
this.app.post('/users', (req, res) => {
const userData = req.body;
// Validation
if (!userData.email || !userData.firstName || !userData.lastName) {
return res.status(400).json({
error: 'Missing required fields',
required: ['email', 'firstName', 'lastName']
});
}
const newUser = {
id: faker.string.uuid(),
...userData,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
isActive: true
};
res.status(201).json(newUser);
});
// Update user
this.app.put('/users/:id', (req, res) => {
const userId = req.params.id;
const updates = req.body;
const updatedUser = {
id: userId,
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
email: faker.internet.email(),
...updates,
updatedAt: new Date().toISOString()
};
res.json(updatedUser);
});
// Delete user
this.app.delete('/users/:id', (req, res) => {
res.status(204).send();
});
// Mock authentication
this.app.post('/auth/login', (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({
error: 'Email and password required'
});
}
// Simulate authentication failure
if (email.includes('invalid')) {
return res.status(401).json({
error: 'Invalid credentials'
});
}
const token = faker.string.alphanumeric(64);
const user = {
id: faker.string.uuid(),
email: email,
firstName: faker.person.firstName(),
lastName: faker.person.lastName()
};
res.json({
token: token,
user: user,
expiresIn: 3600
});
});
// Mock file upload
this.app.post('/upload', (req, res) => {
const fileId = faker.string.uuid();
res.json({
fileId: fileId,
url: https://mock-storage.example.com/files/${fileId},
size: faker.number.int({ min: 1024, max: 1048576 }),
mimeType: faker.helpers.arrayElement([
'image/jpeg', 'image/png', 'application/pdf', 'text/plain'
]),
uploadedAt: new Date().toISOString()
});
});
// Search endpoint
this.app.get('/search', (req, res) => {
const query = req.query.q || '';
const type = req.query.type || 'all';
const results = Array.from({ length: faker.number.int({ min: 0, max: 20 }) }, () => ({
id: faker.string.uuid(),
title: faker.lorem.words(3),
description: faker.lorem.sentence(),
type: faker.helpers.arrayElement(['user', 'document', 'project']),
relevance: faker.number.float({ min: 0, max: 1 }),
createdAt: faker.date.past().toISOString()
}));
res.json({
query: query,
type: type,
results: results,
totalCount: results.length,
searchTime: faker.number.int({ min: 10, max: 200 })
});
});
}
start() {
this.app.listen(this.port, () => {
console.log(Mock API server running on port ${this.port});
console.log(Health check: http://localhost:${this.port}/health);
console.log(Users endpoint: http://localhost:${this.port}/users);
});
}
}
// Start the mock server
const mockServer = new MockApiServer(3001);
mockServer.start();
Set up your own mock API server with our local development tools.
Advanced Mocking Techniques
1. Request Matching and Routing
Implement sophisticated request matching:
class AdvancedMockRouter {
constructor() {
this.routes = [];
this.globalMiddleware = [];
}
addRoute(config) {
this.routes.push({
...config,
id: faker.string.uuid(),
createdAt: new Date().toISOString()
});
}
async handleRequest(request) {
// Apply global middleware
for (const middleware of this.globalMiddleware) {
request = await middleware(request);
}
// Find matching route
const matchedRoute = this.findMatchingRoute(request);
if (!matchedRoute) {
return {
statusCode: 404,
body: { error: 'No matching route found' }
};
}
// Execute route handler
return await this.executeRoute(matchedRoute, request);
}
findMatchingRoute(request) {
for (const route of this.routes) {
if (this.routeMatches(route, request)) {
return route;
}
}
return null;
}
routeMatches(route, request) {
// Method matching
if (route.method && route.method.toUpperCase() !== request.method.toUpperCase()) {
return false;
}
// Path matching (supports wildcards and parameters)
if (route.path && !this.pathMatches(route.path, request.path)) {
return false;
}
// Header matching
if (route.headers) {
for (const [key, value] of Object.entries(route.headers)) {
if (request.headers[key.toLowerCase()] !== value) {
return false;
}
}
}
// Query parameter matching
if (route.query) {
for (const [key, value] of Object.entries(route.query)) {
if (request.query[key] !== value) {
return false;
}
}
}
// Body matching
if (route.bodyMatch) {
if (!this.bodyMatches(route.bodyMatch, request.body)) {
return false;
}
}
// Custom matcher function
if (route.customMatcher) {
return route.customMatcher(request);
}
return true;
}
pathMatches(routePath, requestPath) {
// Convert route path to regex
const regexPath = routePath
.replace(/:[^/]+/g, '([^/]+)') // Parameters
.replace(/\/g, '.'); // Wildcards
const regex = new RegExp(^${regexPath}$);
return regex.test(requestPath);
}
bodyMatches(matcher, body) {
if (typeof matcher === 'function') {
return matcher(body);
}
if (typeof matcher === 'object') {
// Check if all specified fields match
for (const [key, value] of Object.entries(matcher)) {
if (body[key] !== value) {
return false;
}
}
return true;
}
return false;
}
async executeRoute(route, request) {
// Extract path parameters
const params = this.extractPathParams(route.path, request.path);
request.params = params;
// Apply route-specific middleware
if (route.middleware) {
for (const middleware of route.middleware) {
request = await middleware(request);
}
}
// Generate response
if (typeof route.response === 'function') {
return await route.response(request);
} else {
return this.processStaticResponse(route.response, request);
}
}
extractPathParams(routePath, requestPath) {
const routeParts = routePath.split('/');
const requestParts = requestPath.split('/');
const params = {};
for (let i = 0; i < routeParts.length; i++) {
const routePart = routeParts[i];
if (routePart.startsWith(':')) {
const paramName = routePart.slice(1);
params[paramName] = requestParts[i];
}
}
return params;
}
processStaticResponse(response, request) {
// Process template variables in static responses
if (typeof response === 'object') {
return JSON.parse(JSON.stringify(response)
.replace(/\{\{([^}]+)\}\}/g, (match, variable) => {
// Replace template variables
switch (variable) {
case 'timestamp':
return new Date().toISOString();
case 'uuid':
return faker.string.uuid();
case 'randomNumber':
return faker.number.int({ min: 1, max: 1000 });
default:
return request.params[variable] || match;
}
})
);
}
return response;
}
}// Usage example
const router = new AdvancedMockRouter();
// Add global authentication middleware
router.globalMiddleware.push(async (request) => {
if (request.path.startsWith('/api/') && request.method !== 'OPTIONS') {
const authHeader = request.headers.authorization;
if (!authHeader) {
throw new Error('Authentication required');
}
}
return request;
});
// Simple static route
router.addRoute({
method: 'GET',
path: '/api/status',
response: {
statusCode: 200,
body: {
status: 'ok',
timestamp: '{{timestamp}}',
server: 'mock'
}
}
});
// Dynamic route with parameters
router.addRoute({
method: 'GET',
path: '/api/users/:id',
response: async (request) => {
const userId = request.params.id;
return {
statusCode: 200,
body: {
id: userId,
name: faker.person.fullName(),
email: faker.internet.email(),
createdAt: faker.date.past().toISOString()
}
};
}
});
// Conditional route based on query parameters
router.addRoute({
method: 'GET',
path: '/api/users',
query: { admin: 'true' },
response: {
statusCode: 200,
body: {
users: [
{
id: '{{uuid}}',
name: 'Admin User',
role: 'administrator',
permissions: ['read', 'write', 'delete']
}
]
}
}
});
// Route with body matching
router.addRoute({
method: 'POST',
path: '/api/users',
bodyMatch: (body) => body.role === 'admin',
response: {
statusCode: 403,
body: {
error: 'Admin users cannot be created via API',
code: 'FORBIDDEN_OPERATION'
}
}
});
// Fallback route for user creation
router.addRoute({
method: 'POST',
path: '/api/users',
response: async (request) => {
const userData = request.body;
return {
statusCode: 201,
body: {
id: faker.string.uuid(),
...userData,
createdAt: new Date().toISOString()
}
};
}
});
2. Response Scenarios and State
Implement complex scenarios and state management:
class ScenarioBasedMocking {
constructor() {
this.scenarios = new Map();
this.currentScenario = 'default';
this.state = new Map();
this.eventListeners = [];
}
defineScenario(name, config) {
this.scenarios.set(name, {
name: name,
description: config.description || '',
routes: config.routes || [],
initialState: config.initialState || {},
onEnter: config.onEnter || null,
onExit: config.onExit || null
});
}
async switchScenario(scenarioName) {
const currentScenario = this.scenarios.get(this.currentScenario);
const newScenario = this.scenarios.get(scenarioName);
if (!newScenario) {
throw new Error(Scenario '${scenarioName}' not found);
}
// Exit current scenario
if (currentScenario && currentScenario.onExit) {
await currentScenario.onExit(this.state);
}
// Update state
this.state.clear();
Object.entries(newScenario.initialState).forEach(([key, value]) => {
this.state.set(key, value);
});
// Enter new scenario
if (newScenario.onEnter) {
await newScenario.onEnter(this.state);
}
this.currentScenario = scenarioName;
// Notify listeners
this.eventListeners.forEach(listener => {
listener('scenarioChanged', { from: currentScenario?.name, to: scenarioName });
});
}
async handleRequest(request) {
const scenario = this.scenarios.get(this.currentScenario);
if (!scenario) {
throw new Error(Current scenario '${this.currentScenario}' not found);
}
// Find matching route in current scenario
for (const route of scenario.routes) {
if (this.routeMatches(route, request)) {
return await this.executeScenarioRoute(route, request);
}
}
// No matching route in scenario
return {
statusCode: 404,
body: {
error: 'Route not found in current scenario',
scenario: this.currentScenario
}
};
}
routeMatches(route, request) {
return route.method === request.method &&
this.pathMatches(route.path, request.path);
}
pathMatches(routePath, requestPath) {
const regexPath = routePath.replace(/:[^/]+/g, '([^/]+)');
const regex = new RegExp(^${regexPath}$);
return regex.test(requestPath);
}
async executeScenarioRoute(route, request) {
// Check preconditions
if (route.preconditions) {
for (const condition of route.preconditions) {
if (!condition(this.state, request)) {
return {
statusCode: 412,
body: { error: 'Precondition failed' }
};
}
}
}
// Execute route handler
const response = await route.handler(request, this.state);
// Apply state changes
if (route.stateChanges) {
for (const change of route.stateChanges) {
await change(this.state, request, response);
}
}
return response;
}
addEventListener(listener) {
this.eventListeners.push(listener);
}
getScenarioInfo() {
return {
current: this.currentScenario,
available: Array.from(this.scenarios.keys()),
state: Object.fromEntries(this.state)
};
}
}// Usage example with e-commerce scenarios
const scenarioMocking = new ScenarioBasedMocking();
// Default scenario - normal operation
scenarioMocking.defineScenario('default', {
description: 'Normal API operation',
initialState: {
cartItems: 0,
inventory: 100,
paymentStatus: 'available'
},
routes: [
{
method: 'GET',
path: '/api/products',
handler: async (req, state) => ({
statusCode: 200,
body: {
products: Array.from({ length: 10 }, () => ({
id: faker.string.uuid(),
name: faker.commerce.productName(),
price: faker.commerce.price(),
inStock: state.get('inventory') > 0
}))
}
})
},
{
method: 'POST',
path: '/api/cart/add',
handler: async (req, state) => {
const currentItems = state.get('cartItems');
state.set('cartItems', currentItems + 1);
return {
statusCode: 200,
body: {
cartItems: state.get('cartItems'),
message: 'Item added to cart'
}
};
}
}
]
});
// High load scenario - simulates system under stress
scenarioMocking.defineScenario('highLoad', {
description: 'System under high load',
initialState: {
responseDelay: 3000,
errorRate: 0.1,
cartItems: 0
},
routes: [
{
method: 'GET',
path: '/api/products',
handler: async (req, state) => {
// Simulate delay
await new Promise(resolve =>
setTimeout(resolve, state.get('responseDelay'))
);
// Simulate occasional errors
if (Math.random() < state.get('errorRate')) {
return {
statusCode: 503,
body: { error: 'Service temporarily unavailable' }
};
}
return {
statusCode: 200,
body: {
products: Array.from({ length: 5 }, () => ({
id: faker.string.uuid(),
name: faker.commerce.productName(),
price: faker.commerce.price()
}))
}
};
}
}
]
});
// Maintenance scenario - system in maintenance mode
scenarioMocking.defineScenario('maintenance', {
description: 'System in maintenance mode',
initialState: {},
routes: [
{
method: '*',
path: '*',
handler: async () => ({
statusCode: 503,
body: {
error: 'System is currently under maintenance',
maintenanceWindow: {
start: new Date().toISOString(),
estimatedEnd: new Date(Date.now() + 3600000).toISOString()
}
}
})
}
]
});
// Switch between scenarios
console.log('Starting in default scenario');
await scenarioMocking.switchScenario('default');
// Simulate some requests
const testRequests = [
{ method: 'GET', path: '/api/products' },
{ method: 'POST', path: '/api/cart/add', body: { productId: '123' } }
];
for (const req of testRequests) {
const response = await scenarioMocking.handleRequest(req);
console.log('Response:', response);
}
// Switch to high load scenario
console.log('\nSwitching to high load scenario');
await scenarioMocking.switchScenario('highLoad');
Cluster Articles
This pillar page is supported by detailed articles covering specific aspects of API mocking:
Setting up a Local API Mock Server
Learn how to create and configure a complete local mock server environment for development and testing, including Docker integration and automated setup.Best Practices for Mocking REST APIs
Discover proven strategies for creating maintainable, realistic REST API mocks that support comprehensive testing and development workflows.Handling Authentication in Mock API Responses
Master authentication simulation techniques including JWT tokens, OAuth flows, and session management in mock API environments.Using FakerBox for Dynamic API Mocking
Explore how to leverage FakerBox's data generation capabilities to create dynamic, realistic API responses that adapt to your testing needs.Tools and Platforms
Open Source Solutions
Commercial Platforms
FakerBox Integration
Our platform provides comprehensive API mocking capabilities:
Conclusion
API mocking is essential for modern software development, enabling teams to work independently, test comprehensively, and deploy confidently. By implementing the strategies and techniques covered in this guide, you'll be able to:
Key principles to remember:
Ready to master API mocking for your development workflow? Start with our comprehensive API mocking tools and transform your testing strategy today.
Have questions about implementing API mocking for your specific use case? Contact our API experts for personalized guidance.