Back to Blog
API Testing
June 30, 2024
18 min read

Mastering API Mocking for Efficient Software Testing

Complete guide to API mocking strategies, tools, and best practices for faster development and more reliable testing across all environments.

api mocking
software testing
development tools
REST APIs
testing strategies

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-time

Generate 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

  • JSON Server - Quick REST API mocking
  • WireMock - Flexible HTTP service virtualization
  • MSW (Mock Service Worker) - Browser and Node.js API mocking
  • Prism - OpenAPI-powered mock server
  • Commercial Platforms

  • Postman Mock Server - Cloud-based API mocking
  • Stoplight Prism - Contract-driven mock servers
  • MockLab - Enterprise WireMock hosting
  • Hoverfly - Service virtualization platform
  • FakerBox Integration

    Our platform provides comprehensive API mocking capabilities:

  • Dynamic Response Generator - Create realistic API responses
  • Mock Server Templates - Pre-built server configurations
  • Authentication Simulators - Mock auth flows and tokens
  • Error Scenario Generator - Comprehensive error testing
  • 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:

  • • Create realistic, maintainable API mocks
  • • Implement sophisticated testing scenarios
  • • Enable parallel development workflows
  • • Improve application reliability and performance
  • • Reduce dependencies on external services
  • Key principles to remember:

  • • Start with contract-first development
  • • Implement realistic response patterns
  • • Use stateful mocks for complex scenarios
  • • Automate mock setup and maintenance
  • • Integrate mocking into your CI/CD pipeline
  • 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.

    Ready to Generate Test Data?

    Put these best practices into action with our comprehensive data generation tools.

    Related Articles

    Development
    20 min read

    The Ultimate Guide to Test Data Generation

    Comprehensive resource covering everything from basic fake data generation to advanced synthetic data strategies for modern development teams.