Jul 17, 2025

How to Mock in Integration Tests Tools and Implementation

Mock smarter in integration testing. Explore tools like Nock, Sinon, and Jest with hands-on examples and a step-by-step guide to accurate and efficient mocks.

Author

Nilesh KumarSoftware Engineer - II
How to Mock in Integration Tests Tools and Implementation

Having established when and why to mock in integration tests, it's time to explore the practical implementation of mocking strategies. This comprehensive guide covers the essential tools, techniques, and step-by-step processes for implementing effective mocks in your integration tests. We'll dive deep into popular mocking libraries, provide detailed examples, and explore advanced techniques for maintaining reliable and accurate mocks.

Essential Mocking Tools and Libraries

The JavaScript ecosystem offers several powerful tools for implementing mocks in integration tests. Each tool serves different purposes and excels in specific scenarios. Understanding their strengths and use cases will help you choose the right tool for your specific testing needs.

Nock: HTTP Request Mocking

Nock is the most popular and powerful HTTP request mocking library for Node.js. It allows you to intercept and mock HTTP requests at the network level, making it ideal for testing applications that interact with external APIs.

Key Features:

  • Intercepts HTTP requests at the network level
  • Supports complex request matching patterns
  • Provides detailed request/response validation
  • Offers recording and playback capabilities
  • Integrates seamlessly with all testing frameworks
  • Supports both REST and GraphQL APIs

When to Use Nock:

  • Testing interactions with external REST APIs
  • Mocking third-party services like payment gateways
  • Testing error handling for HTTP failures
  • Validating request formats and headers
  • Creating deterministic responses for external services

Basic Nock Usage:

const nock = require('nock');

// Simple GET request mock
nock('<https://api.github.com>')
  .get('/users/octocat')
  .reply(200, {
    login: 'octocat',
    id: 1,
    name: 'The Octocat',
    company: 'GitHub'
  });

// POST request with request body validation
nock('<https://api.stripe.com>')
  .post('/v1/charges')
  .matchHeader('authorization', /^Bearer sk_test_/)
  .reply((uri, requestBody) => {
    // Validate request body
    const data = new URLSearchParams(requestBody);
    if (!data.get('amount') || !data.get('currency')) {
      return [400, { error: 'Missing required parameters' }];
    }

    return [200, {
      id: 'ch_test_123',
      amount: parseInt(data.get('amount')),
      currency: data.get('currency'),
      status: 'succeeded'
    }];
  });

// Query parameter matching
nock('<https://api.weather.com>')
  .get('/v1/current')
  .query({ key: 'test-api-key', q: 'London' })
  .reply(200, {
    location: { name: 'London' },
    current: { temp_c: 15, condition: { text: 'Cloudy' } }
  });

Sinon: Comprehensive Function Mocking

Sinon is a versatile library that provides spies, stubs, and mocks for JavaScript functions and objects. It's particularly useful for mocking internal dependencies and complex object interactions.

Key Features:

  • Function spies for monitoring calls
  • Stubs for replacing function behavior
  • Mocks for complex object interactions
  • Fake timers for time-based testing
  • Extensive assertion capabilities
  • Works with any testing framework

When to Use Sinon:

  • Mocking internal service dependencies
  • Testing time-based functionality
  • Spying on function calls and arguments
  • Stubbing complex object methods
  • Testing callback and promise behavior

Basic Sinon Usage:

const sinon = require('sinon');

// Creating stubs
const emailService = {
  sendEmail: sinon.stub()
};

// Configure stub behavior
emailService.sendEmail
  .withArgs('user@example.com')
  .resolves({ messageId: 'test-123' })
  .withArgs('invalid@email')
  .rejects(new Error('Invalid email address'));

// Spying on existing functions
const userService = require('../services/UserService');
const getUserSpy = sinon.spy(userService, 'getUser');

// Fake timers for time-based testing
const clock = sinon.useFakeTimers();
clock.tick(1000); // Advance time by 1 second
clock.restore();

// Stub with dynamic behavior
const databaseStub = sinon.stub();
databaseStub.callsFake((query) => {
  if (query.includes('SELECT')) {
    return Promise.resolve([{ id: 1, name: 'Test User' }]);
  } else if (query.includes('INSERT')) {
    return Promise.resolve({ insertedId: 123 });
  }
  return Promise.reject(new Error('Unsupported query'));
});

Jest Built-in Mocking

Jest provides powerful built-in mocking capabilities that integrate seamlessly with the testing framework. While not as specialized as nock or sinon, Jest mocking is convenient for simple scenarios and module-level mocking.

Key Features:

  • Module mocking with automatic mock generation
  • Function mocking with call tracking
  • Timer mocking for time-based tests
  • Snapshot testing for mocked responses
  • Mock clearing and restoration utilities

When to Use Jest Mocking:

  • Simple function mocking scenarios
  • Module-level mocking
  • Quick prototyping of mocks
  • Integration with Jest snapshot testing

Basic Jest Mocking:

// Module mocking
jest.mock('../services/EmailService');
const EmailService = require('../services/EmailService');

// Function mocking
const mockSendEmail = jest.fn();
EmailService.prototype.sendEmail = mockSendEmail;

// Configure mock behavior
mockSendEmail.mockResolvedValue({ messageId: 'test-123' });
mockSendEmail.mockRejectedValueOnce(new Error('Email service unavailable'));

// Timer mocking
jest.useFakeTimers();
jest.advanceTimersByTime(1000);
jest.useRealTimers();

// Spy on modules
const userService = require('../services/UserService');
const getUserSpy = jest.spyOn(userService, 'getUser');
getUserSpy.mockResolvedValue({ id: 1, name: 'Test User' });

Step-by-Step Implementation Guide

Let's walk through a comprehensive example of implementing integration tests with strategic mocking. We'll build a notification system that demonstrates different mocking scenarios and techniques.

Application Architecture

Our example application is a user notification system with the following components:

// models/User.js
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  profile: {
    firstName: String,
    lastName: String,
    preferences: {
      emailNotifications: { type: Boolean, default: true },
      smsNotifications: { type: Boolean, default: false }
    }
  },
  lastNotificationDate: Date,
  createdAt: { type: Date, default: Date.now }
});

module.exports = mongoose.model('User', userSchema);

// services/EmailService.js
const nodemailer = require('nodemailer');

class EmailService {
  constructor() {
    this.transporter = nodemailer.createTransporter({
      host: process.env.SMTP_HOST,
      port: process.env.SMTP_PORT,
      auth: {
        user: process.env.SMTP_USER,
        pass: process.env.SMTP_PASS
      }
    });
  }

  async sendEmail(to, subject, htmlBody) {
    try {
      const result = await this.transporter.sendMail({
        from: process.env.FROM_EMAIL,
        to,
        subject,
        html: htmlBody
      });

      return {
        success: true,
        messageId: result.messageId,
        timestamp: new Date()
      };
    } catch (error) {
      console.error('Email sending failed:', error);
      throw new Error(`Failed to send email: ${error.message}`);
    }
  }

  async sendBulkEmail(recipients, subject, htmlBody) {
    const results = [];

    for (const recipient of recipients) {
      try {
        const result = await this.sendEmail(recipient, subject, htmlBody);
        results.push({ email: recipient, ...result });
      } catch (error) {
        results.push({
          email: recipient,
          success: false,
          error: error.message
        });
      }
    }

    return results;
  }
}

module.exports = EmailService;

// services/ExternalApiService.js
const axios = require('axios');

class ExternalApiService {
  constructor() {
    this.baseURL = process.env.EXTERNAL_API_URL;
    this.apiKey = process.env.EXTERNAL_API_KEY;
    this.timeout = 5000;
  }

  async getUserPreferences(userId) {
    try {
      const response = await axios.get(
        `${this.baseURL}/users/${userId}/preferences`,
        {
          headers: {
            'Authorization': `Bearer ${this.apiKey}`,
            'Content-Type': 'application/json'
          },
          timeout: this.timeout
        }
      );

      return {
        success: true,
        data: response.data,
        timestamp: new Date()
      };
    } catch (error) {
      if (error.response) {
        throw new Error(`API Error: ${error.response.status} - ${error.response.data.message}`);
      } else if (error.code === 'ECONNABORTED') {
        throw new Error('API request timeout');
      } else {
        throw new Error(`Network error: ${error.message}`);
      }
    }
  }

  async updateUserActivity(userId, activity) {
    try {
      const response = await axios.post(
        `${this.baseURL}/users/${userId}/activity`,
        activity,
        {
          headers: {
            'Authorization': `Bearer ${this.apiKey}`,
            'Content-Type': 'application/json'
          },
          timeout: this.timeout
        }
      );

      return response.data;
    } catch (error) {
      console.error('Failed to update user activity:', error);
      // Don't throw - this is non-critical
      return { success: false, error: error.message };
    }
  }
}

module.exports = ExternalApiService;

// services/NotificationService.js
const User = require('../models/User');
const EmailService = require('./EmailService');
const ExternalApiService = require('./ExternalApiService');

class NotificationService {
  constructor() {
    this.emailService = new EmailService();
    this.externalApiService = new ExternalApiService();
  }

  async sendWelcomeNotification(userId) {
    try {
      // Get user from database (keep real - core integration)
      const user = await User.findById(userId);
      if (!user) {
        throw new Error('User not found');
      }

      // Get user preferences from external API (mock this - external dependency)
      const preferencesResult = await this.externalApiService.getUserPreferences(userId);
      const preferences = preferencesResult.data;

      let emailSent = false;

      // Send email if user has email notifications enabled
      if (preferences.emailNotifications) {
        const htmlBody = `
          <h1>Welcome, ${user.profile.firstName}!</h1>
          <p>Thank you for joining our platform.</p>
          <p>We're excited to have you on board!</p>
        `;

        // Send email (mock this - external service)
        await this.emailService.sendEmail(
          user.email,
          'Welcome to Our Platform!',
          htmlBody
        );
        emailSent = true;
      }

      // Update user's last notification date (keep real - core integration)
      await User.findByIdAndUpdate(userId, {
        lastNotificationDate: new Date()
      });

      // Track activity in external system (mock this - external dependency)
      await this.externalApiService.updateUserActivity(userId, {
        type: 'welcome_notification_sent',
        timestamp: new Date(),
        emailSent
      });

      return {
        success: true,
        userId,
        emailSent,
        timestamp: new Date()
      };
    } catch (error) {
      console.error('Welcome notification failed:', error);
      throw error;
    }
  }

  async sendBulkPromotionalEmail(userIds, promotion) {
    const results = [];

    for (const userId of userIds) {
      try {
        const user = await User.findById(userId);
        if (!user) {
          results.push({
            userId,
            success: false,
            error: 'User not found'
          });
          continue;
        }

        // Check preferences
        const preferencesResult = await this.externalApiService.getUserPreferences(userId);

        if (!preferencesResult.data.emailNotifications) {
          results.push({
            userId,
            success: true,
            emailSent: false,
            reason: 'Email notifications disabled'
          });
          continue;
        }

        // Send promotional email
        const htmlBody = `
          <h1>Special Offer for ${user.profile.firstName}!</h1>
          <h2>${promotion.title}</h2>
          <p>${promotion.description}</p>
		  <p>Claim Your Offer</p>
        `;

        await this.emailService.sendEmail(
          user.email,
          promotion.title,
          htmlBody
        );

        results.push({
          userId,
          success: true,
          emailSent: true
        });

      } catch (error) {
        results.push({
          userId,
          success: false,
          error: error.message
        });
      }
    }

    return {
      totalProcessed: results.length,
      successful: results.filter(r => r.success).length,
      emailsSent: results.filter(r => r.emailSent).length,
      results
    };
  }
}

module.exports = NotificationService;

Step 1: Test Environment Setup

// tests/integration/notification.integration.test.js
const mongoose = require('mongoose');
const nock = require('nock');
const sinon = require('sinon');
const NotificationService = require('../../services/NotificationService');
const EmailService = require('../../services/EmailService');
const User = require('../../models/User');

describe('Notification Service Integration Tests', () => {
  let notificationService;

  beforeAll(async () => {
    // Connect to test database
    const testDbUrl = process.env.TEST_DATABASE_URL || 'mongodb://localhost:27017/test_notifications';
    await mongoose.connect(testDbUrl);

    // Set up environment variables for testing
    process.env.EXTERNAL_API_URL = '<https://api.external-service.com>';
    process.env.EXTERNAL_API_KEY = 'test-api-key';
    process.env.FROM_EMAIL = 'noreply@testapp.com';
  });

  afterAll(async () => {
    // Clean up database connection
    await mongoose.connection.close();

    // Clean up nock interceptors
    nock.cleanAll();
  });

  beforeEach(async () => {
    // Clear database before each test
    await User.deleteMany({});

    // Create fresh service instance
    notificationService = new NotificationService();

    // Reset all stubs
    sinon.restore();

    // Clean up any existing nock interceptors
    nock.cleanAll();
  });

  afterEach(() => {
    // Clean up nock interceptors after each test
    nock.cleanAll();

    // Restore all sinon stubs
    sinon.restore();
  });

Step 2: Basic Integration Test with Mocking

 describe('sendWelcomeNotification', () => {
    it('should send welcome notification with email enabled', async () => {
      // Setup: Create a real user in the database (authentic integration)
      const user = new User({
        email: 'john.doe@example.com',
        password: 'hashedpassword123',
        profile: {
          firstName: 'John',
          lastName: 'Doe',
          preferences: {
            emailNotifications: true
          }
        }
      });
      await user.save();

      // Mock external API call (external dependency - should be mocked)
      const mockPreferences = {
        emailNotifications: true,
        smsNotifications: false,
        marketingEmails: true
      };

      nock('<https://api.external-service.com>')
        .get(`/users/${user._id}/preferences`)
        .matchHeader('authorization', 'Bearer test-api-key')
        .reply(200, mockPreferences);

      // Mock activity tracking API call (external dependency - should be mocked)
      nock('<https://api.external-service.com>')
        .post(`/users/${user._id}/activity`)
        .matchHeader('authorization', 'Bearer test-api-key')
        .reply(200, { success: true, activityId: 'activity-123' });

      // Mock email service (external dependency - should be mocked)
      const emailSendStub = sinon.stub(EmailService.prototype, 'sendEmail');
      emailSendStub.resolves({
        success: true,
        messageId: 'test-message-id-123',
        timestamp: new Date()
      });

      // Execute the test
      const result = await notificationService.sendWelcomeNotification(user._id);

      // Verify the result structure
      expect(result.success).toBe(true);
      expect(result.userId).toEqual(user._id);
      expect(result.emailSent).toBe(true);
      expect(result.timestamp).toBeInstanceOf(Date);

      // Verify email service was called correctly
      expect(emailSendStub.calledOnce).toBe(true);
      const emailCall = emailSendStub.firstCall;
      expect(emailCall.args[0]).toBe('john.doe@example.com');
      expect(emailCall.args[1]).toBe('Welcome to Our Platform!');
      expect(emailCall.args[2]).toContain('Welcome, John!');

      // Verify database was updated (real integration - should be tested)
      const updatedUser = await User.findById(user._id);
      expect(updatedUser.lastNotificationDate).toBeTruthy();
      expect(updatedUser.lastNotificationDate).toBeInstanceOf(Date);

      // Verify all nock interceptors were called
      expect(nock.isDone()).toBe(true);
    });

    it('should skip email when notifications are disabled', async () => {
      // Setup: Create a real user
      const user = new User({
        email: 'jane.doe@example.com',
        password: 'hashedpassword123',
        profile: {
          firstName: 'Jane',
          lastName: 'Doe'
        }
      });
      await user.save();

      // Mock external API with disabled email notifications
      const mockPreferences = {
        emailNotifications: false,
        smsNotifications: true,
        marketingEmails: false
      };

      nock('<https://api.external-service.com>')
        .get(`/users/${user._id}/preferences`)
        .reply(200, mockPreferences);

      // Mock activity tracking
      nock('<https://api.external-service.com>')
        .post(`/users/${user._id}/activity`)
        .reply(200, { success: true });

      // Mock email service (should not be called)
      const emailSendStub = sinon.stub(EmailService.prototype, 'sendEmail');

      // Execute the test
      const result = await notificationService.sendWelcomeNotification(user._id);

      // Verify the result
      expect(result.success).toBe(true);
      expect(result.emailSent).toBe(false);

      // Verify email service was not called
      expect(emailSendStub.called).toBe(false);

      // Verify database was still updated
      const updatedUser = await User.findById(user._id);
      expect(updatedUser.lastNotificationDate).toBeTruthy();

      // Verify all expected API calls were made
      expect(nock.isDone()).toBe(true);
    });
  });

Step 3: Advanced Mocking Scenarios

describe('Error Handling and Edge Cases', () => {
    it('should handle external API timeout gracefully', async () => {
      // Setup user
      const user = new User({
        email: 'timeout@example.com',
        password: 'password123',
        profile: { firstName: 'Timeout' }
      });
      await user.save();

      // Mock API timeout
      nock('<https://api.external-service.com>')
        .get(`/users/${user._id}/preferences`)
        .delayConnection(6000) // Longer than service timeout
        .reply(200, { emailNotifications: true });

      // Execute and expect failure
      await expect(
        notificationService.sendWelcomeNotification(user._id)
      ).rejects.toThrow('API request timeout');

      // Verify no email was sent
      const emailSendStub = sinon.stub(EmailService.prototype, 'sendEmail');
      expect(emailSendStub.called).toBe(false);
    });

    it('should handle external API error responses', async () => {
      // Setup user
      const user = new User({
        email: 'error@example.com',
        password: 'password123',
        profile: { firstName: 'Error' }
      });
      await user.save();

      // Mock API error response
      nock('<https://api.external-service.com>')
        .get(`/users/${user._id}/preferences`)
        .reply(500, {
          error: 'Internal Server Error',
          message: 'Database connection failed'
        });

      // Execute and expect specific error
      await expect(
        notificationService.sendWelcomeNotification(user._id)
      ).rejects.toThrow('API Error: 500 - Database connection failed');
    });

    it('should handle email service failures', async () => {
      // Setup user
      const user = new User({
        email: 'emailfail@example.com',
        password: 'password123',
        profile: { firstName: 'EmailFail' }
      });
      await user.save();

      // Mock successful API calls
      nock('<https://api.external-service.com>')
        .get(`/users/${user._id}/preferences`)
        .reply(200, { emailNotifications: true });

      nock('<https://api.external-service.com>')
        .post(`/users/${user._id}/activity`)
        .reply(200, { success: true });

      // Mock email service failure
      const emailSendStub = sinon.stub(EmailService.prototype, 'sendEmail');
      emailSendStub.rejects(new Error('SMTP server unavailable'));

      // Execute and expect failure
      await expect(
        notificationService.sendWelcomeNotification(user._id)
      ).rejects.toThrow('SMTP server unavailable');

      // Verify database was not updated due to transaction failure
      const user_after = await User.findById(user._id);
      expect(user_after.lastNotificationDate).toBeFalsy();
    });
  });

  describe('sendBulkPromotionalEmail', () => {
    it('should handle mixed success and failure scenarios', async () => {
      // Setup: Create multiple real users
      const users = await Promise.all([
        new User({
          email: 'bulk1@example.com',
          password: 'password',
          profile: { firstName: 'Bulk1' }
        }).save(),
        new User({
          email: 'bulk2@example.com',
          password: 'password',
          profile: { firstName: 'Bulk2' }
        }).save(),
        new User({
          email: 'bulk3@example.com',
          password: 'password',
          profile: { firstName: 'Bulk3' }
        }).save()
      ]);

      const userIds = users.map(u => u._id);

      // Mock external API responses - mixed scenarios
      nock('<https://api.external-service.com>')
        .get(`/users/${userIds[0]}/preferences`)
        .reply(200, { emailNotifications: true });

      nock('<https://api.external-service.com>')
        .get(`/users/${userIds[1]}/preferences`)
        .reply(200, { emailNotifications: false });

      nock('<https://api.external-service.com>')
        .get(`/users/${userIds[2]}/preferences`)
        .reply(500, { error: 'Server Error' });

      // Mock email service - successful for enabled users
      const emailSendStub = sinon.stub(EmailService.prototype, 'sendEmail');
      emailSendStub.resolves({
        success: true,
        messageId: 'bulk-message-id',
        timestamp: new Date()
      });

      const promotion = {
        title: 'Summer Sale - 50% Off!',
        description: 'Limited time offer on all products.',
        link: '<https://example.com/summer-sale>'
      };

      // Execute the test
      const result = await notificationService.sendBulkPromotionalEmail(userIds, promotion);

      // Verify overall results
      expect(result.totalProcessed).toBe(3);
      expect(result.successful).toBe(2); // First two users
      expect(result.emailsSent).toBe(1); // Only first user
      expect(result.results).toHaveLength(3);

      // Verify individual results
      expect(result.results[0].success).toBe(true);
      expect(result.results[0].emailSent).toBe(true);

      expect(result.results[1].success).toBe(true);
      expect(result.results[1].emailSent).toBe(false);
      expect(result.results[1].reason).toBe('Email notifications disabled');

      expect(result.results[2].success).toBe(false);
      expect(result.results[2].error).toContain('API Error: 500');

      // Verify email service was called only once (for first user)
      expect(emailSendStub.calledOnce).toBe(true);
      expect(emailSendStub.firstCall.args[0]).toBe('bulk1@example.com');
      expect(emailSendStub.firstCall.args[1]).toBe('Summer Sale - 50% Off!');
    });
  });

Step 4: Performance Testing with Mocks

describe('Performance Testing', () => {
    it('should execute quickly with mocked external services', async () => {
      // Setup multiple users for performance testing
      const userCount = 20;
      const users = await Promise.all(
        Array.from({ length: userCount }, (_, i) =>
          new User({
            email: `perf${i}@example.com`,
            password: 'password',
            profile: { firstName: `PerfUser${i}` }
          }).save()
        )
      );

      const userIds = users.map(u => u._id);

      // Mock all external API calls with realistic delays
      userIds.forEach(userId => {
        nock('<https://api.external-service.com>')
          .get(`/users/${userId}/preferences`)
          .delay(50) // Realistic API delay
          .reply(200, { emailNotifications: true });
      });

      // Mock email service with realistic delay
      const emailSendStub = sinon.stub(EmailService.prototype, 'sendEmail');
      emailSendStub.callsFake(() => {
        return new Promise(resolve => {
          setTimeout(() => {
            resolve({
              success: true,
              messageId: `msg-${Date.now()}`,
              timestamp: new Date()
            });
          }, 30); // Realistic email sending delay
        });
      });

      const promotion = {
        title: 'Performance Test Promotion',
        description: 'Testing bulk email performance',
        link: '<https://example.com/test>'
      };

      const startTime = Date.now();

      // Execute bulk operation
      const result = await notificationService.sendBulkPromotionalEmail(userIds, promotion);

      const executionTime = Date.now() - startTime;

      // Verify results
      expect(result.totalProcessed).toBe(userCount);
      expect(result.successful).toBe(userCount);
      expect(result.emailsSent).toBe(userCount);

      // Verify performance expectations
      expect(executionTime).toBeLessThan(5000); // Should complete in under 5 seconds
      console.log(`Bulk email processing took ${executionTime}ms for ${userCount} users`);

      // Verify email service was called for each user
      expect(emailSendStub.callCount).toBe(userCount);
    });

    it('should handle concurrent operations efficiently', async () => {
      // Setup users
      const users = await Promise.all([
        new User({
          email: 'concurrent1@example.com',
          password: 'password',
          profile: { firstName: 'Concurrent1' }
        }).save(),
        new User({
          email: 'concurrent2@example.com',
          password: 'password',
          profile: { firstName: 'Concurrent2' }
        }).save()
      ]);

      // Mock external services for both users
      users.forEach(user => {
        nock('<https://api.external-service.com>')
          .get(`/users/${user._id}/preferences`)
          .reply(200, { emailNotifications: true });

        nock('<https://api.external-service.com>')
          .post(`/users/${user._id}/activity`)
          .reply(200, { success: true });
      });

      // Mock email service
      const emailSendStub = sinon.stub(EmailService.prototype, 'sendEmail');
      emailSendStub.resolves({ success: true, messageId: 'concurrent-test' });

      // Execute concurrent operations
      const startTime = Date.now();

      const results = await Promise.all(
        users.map(user =>
          notificationService.sendWelcomeNotification(user._id)
        )
      );

      const executionTime = Date.now() - startTime;

      // Verify all operations succeeded
      results.forEach(result => {
        expect(result.success).toBe(true);
        expect(result.emailSent).toBe(true);
      });

      // Verify performance (concurrent operations should be faster)
      expect(executionTime).toBeLessThan(1000);
      console.log(`Concurrent operations took ${executionTime}ms`);
    });
  });

Advanced Mocking Techniques

Dynamic Response Generation

Creating mocks that respond intelligently to different request parameters:

describe('Dynamic Mock Responses', () => {
  it('should generate responses based on request parameters', async () => {
    // Create a mock that responds differently based on user ID
    nock('<https://api.external-service.com>')
      .get(/\\/users\\/\\w+\\/preferences/)
      .reply((uri) => {
        const userId = uri.split('/')[2];

        // Generate different responses based on user ID pattern
        if (userId.includes('vip')) {
          return [200, {
            emailNotifications: true,
            smsNotifications: true,
            premiumFeatures: true,
            tier: 'VIP'
          }];
        } else if (userId.includes('basic')) {
          return [200, {
            emailNotifications: true,
            smsNotifications: false,
            premiumFeatures: false,
            tier: 'Basic'
          }];
        } else {
          return [404, { error: 'User not found' }];
        }
      });

    // Test with VIP user
    const vipUser = new User({
      email: 'vip@example.com',
      password: 'password',
      profile: { firstName: 'VIP' }
    });
    await vipUser.save();

    // Override the user ID to match our pattern
    const vipUserId = 'vip-user-123';

    // Mock the user lookup to return our VIP user but with the special ID
    const userFindStub = sinon.stub(User, 'findById');
    userFindStub.withArgs(vipUserId).resolves(vipUser);
    userFindStub.callThrough();

    // Mock email service
    const emailSendStub = sinon.stub(EmailService.prototype, 'sendEmail');
    emailSendStub.resolves({ success: true });

    // Mock activity tracking
    nock('<https://api.external-service.com>')
      .post(`/users/${vipUserId}/activity`)
      .reply(200, { success: true });

    const result = await notificationService.sendWelcomeNotification(vipUserId);

    expect(result.success).toBe(true);
    expect(nock.isDone()).toBe(true);
  });
});

Conditional Mocking Based on Environment

describe('Environment-Specific Mocking', () => {
  beforeEach(() => {
    // Store original environment
    this.originalEnv = process.env.NODE_ENV;
  });

  afterEach(() => {
    // Restore original environment
    process.env.NODE_ENV = this.originalEnv;
  });

  it('should use detailed mocks in development', async () => {
    process.env.NODE_ENV = 'development';

    // Use more detailed, verbose mocks in development
    nock('<https://api.external-service.com>')
      .get(/\\/users\\/\\w+\\/preferences/)
      .reply(200, {
        emailNotifications: true,
        smsNotifications: false,
        preferences: {
          newsletter: true,
          promotions: false,
          reminders: true
        },
        metadata: {
          lastUpdated: new Date().toISOString(),
          source: 'user_settings',
          version: '2.1'
        },
        debugInfo: {
          queryTime: '45ms',
          cacheHit: false
        }
      });

    // Test implementation...
  });

  it('should use minimal mocks in CI/CD', async () => {
    process.env.NODE_ENV = 'test';

    // Use minimal, fast mocks in CI/CD
    nock('<https://api.external-service.com>')
      .get(/\\/users\\/\\w+\\/preferences/)
      .reply(200, {
        emailNotifications: true,
        smsNotifications: false
      });

    // Test implementation...
  });
});

Request Recording and Playback

describe('Request Recording and Playback', () => {
  it('should record and replay real API interactions', async () => {
    // This test demonstrates how to record real API calls
    // and replay them in subsequent test runs

    const recordingsPath = './test/fixtures/api-recordings.json';

    // Check if we're in recording mode
    if (process.env.RECORD_API_CALLS === 'true') {
      // Enable nock recording
      nock.recorder.rec({
        dont_print: true,
        output_objects: true
      });

      // Make real API call (only when recording)
      // This would typically be done manually or in a separate recording session

    } else {
      // Load and use recorded interactions
      const fs = require('fs');
      if (fs.existsSync(recordingsPath)) {
        const recordings = JSON.parse(fs.readFileSync(recordingsPath, 'utf8'));
        nock.define(recordings);
      } else {
        // Fallback to manual mocks if no recordings exist
        nock('<https://api.external-service.com>')
          .get(/\\/users\\/\\w+\\/preferences/)
          .reply(200, { emailNotifications: true });
      }
    }

    // Continue with test implementation...
  });
});

Mock Validation and Maintenance

Ensuring Mock Accuracy

describe('Mock Validation', () => {
  it('should verify all expected requests were made', async () => {
    const user = new User({
      email: 'validation@example.com',
      password: 'password',
      profile: { firstName: 'Validation' }
    });
    await user.save();

    // Create interceptors with specific expectations
    const preferencesInterceptor = nock('<https://api.external-service.com>')
      .get(`/users/${user._id}/preferences`)
      .reply(200, { emailNotifications: true });

    const activityInterceptor = nock('<https://api.external-service.com>')
      .post(`/users/${user._id}/activity`)
      .reply(200, { success: true });

    // Mock email service
    const emailSendStub = sinon.stub(EmailService.prototype, 'sendEmail');
    emailSendStub.resolves({ success: true });

    // Execute test
    await notificationService.sendWelcomeNotification(user._id);

    // Verify all interceptors were used
    expect(preferencesInterceptor.isDone()).toBe(true);
    expect(activityInterceptor.isDone()).toBe(true);
    expect(emailSendStub.calledOnce).toBe(true);

    // Verify no unexpected requests were made
    expect(nock.pendingMocks()).toHaveLength(0);
  });

  it('should detect unexpected API calls', async () => {
    const user = new User({
      email: 'unexpected@example.com',
      password: 'password',
      profile: { firstName: 'Unexpected' }
    });
    await user.save();

    // Only mock preferences call, not activity call
    nock('<https://api.external-service.com>')
      .get(`/users/${user._id}/preferences`)
      .reply(200, { emailNotifications: true });

    // Don't mock the activity call - this should cause an error

    // Mock email service
    const emailSendStub = sinon.stub(EmailService.prototype, 'sendEmail');
    emailSendStub.resolves({ success: true });

    // This should fail because the activity API call isn't mocked
    await expect(
      notificationService.sendWelcomeNotification(user._id)
    ).rejects.toThrow();
  });
});

Best Practices for Mock Implementation

1. Keep Mocks Simple and Focused

Avoid overly complex mocks that become difficult to maintain:
// Good: Simple, focused mock
nock('<https://api.example.com>')
  .get('/user/123')
  .reply(200, { id: 123, name: 'Test User' });

// Avoid: Overly complex mock with unnecessary logic
nock('<https://api.example.com>')
  .get('/user/123')
  .reply((uri, requestBody) => {
    // Complex business logic in mock
    const userId = uri.split('/').pop();
    const userData = complexUserDataGeneration(userId);
    const processedData = businessLogicProcessing(userData);
    return [200, processedData];
  });

2. Use Realistic Data and Delays

Make your mocks representative of real system behavior:

// Good: Realistic data and timing
nock('<https://api.example.com>')
  .get('/users/123')
  .delay(100) // Realistic API delay
  .reply(200, {
    id: 123,
    email: 'realistic@example.com',
    createdAt: '2024-01-15T10:30:00Z',
    profile: {
      firstName: 'John',
      lastName: 'Doe'
    }
  });

// Avoid: Unrealistic immediate responses with fake data
nock('<https://api.example.com>')
  .get('/users/123')
  .reply(200, { id: 123, name: 'test' });

3. Mock at the Appropriate Level

Choose the right abstraction level for your mocks:

// Good: Mock at service boundary
const emailServiceStub = sinon.stub(EmailService.prototype, 'sendEmail');
emailServiceStub.resolves({ messageId: 'test-123' });

// Avoid: Mocking too deep into implementation
const nodemailerStub = sinon.stub(nodemailer, 'createTransporter');
const transporterStub = { sendMail: sinon.stub() };
nodemailerStub.returns(transporterStub);

4. Document Mock Decisions

describe('User Notification Tests', () => {
  /**
   * These tests mock the external preference API because:
   * 1. The API has rate limiting that would slow down tests
   * 2. The API requires complex authentication setup
   * 3. The API is owned by a third party and may be unreliable
   *
   * We keep database operations real because:
   * 1. Database interaction is core to our service
   * 2. We want to test real data persistence
   * 3. Test database is fast and reliable
   */
  it('should handle user preferences correctly', async () => {
    // Test implementation...
  });
});

Conclusion

Implementing effective mocks in integration tests requires careful consideration of tools, techniques, and maintenance strategies. The key is to use mocks strategically to eliminate problematic external dependencies while preserving the authentic integrations that provide the most value.

Nock excels at HTTP request mocking and is essential for testing external API integrations. Sinon provides comprehensive function and object mocking capabilities for internal dependencies. Jest's built-in mocking works well for simple scenarios and integrates seamlessly with the testing framework.

The examples in this guide demonstrate how to implement mocks that are realistic, maintainable, and provide genuine testing value. By following these patterns and best practices, you can create integration tests that give you confidence in your system's behavior while remaining practical to execute and maintain.

Remember that mocks are tools to enable effective testing, not goals in themselves. Always evaluate whether your mocking strategy is serving your testing objectives and adjust as needed. The most effective integration test suites strike the right balance between authentic integration testing and practical execution constraints.

As your application evolves, regularly review and update your mocking strategies to ensure they continue to provide value and accurately represent the systems they're replacing. Well-implemented mocks can significantly improve your testing capabilities, but poorly implemented mocks can create false confidence and maintenance overhead.

Subscribe to Our Newsletter

RELATED ARTICLES

More from the engineering frontline.

Dive deep into our research and insights on design, development, and the impact of various trends to businesses.
The Bug That Doesn't Show Up in Code Review: Why Your Flutter Web App Reloads on Safari

Aug 19, 2026

The Bug That Doesn't Show Up in Code Review: Why Your Flutter Web App Reloads on Safari
A real-world look at how oversized images can trigger Safari reloads and iOS crashes in Flutter apps and how smarter image decoding prevents them.
From Prompting to Process: What Changed When Flutter Shipped Agent Skills

Aug 19, 2026

From Prompting to Process: What Changed When Flutter Shipped Agent Skills
This blog explores how Flutter Agent Skills improve AI-assisted development by combining official framework workflows with project-specific guidance for more consistent development.
Why Everything Your AI Builds Looks the Same

Aug 19, 2026

Why Everything Your AI Builds Looks the Same
This blog explores why AI-generated interfaces often look alike and explains how design systems, product context, and reusable engineering practices help teams build distinctive, scalable
How We Built the Missing Bridge from Code to Figma
Technology

Jul 10, 2026

How We Built the Missing Bridge from Code to Figma
This blog explores how AI-generated React apps get turned into fully editable, designer-ready Figma files by reading React Fiber instead of the DOM.
Building a Resilient Hybrid-Cloud Network with WireGuard HA, Route-Based Failover, and Deep Observability
Technology

Jun 27, 2026

Building a Resilient Hybrid-Cloud Network with WireGuard HA, Route-Based Failover, and Deep Observability
A practical breakdown of building resilient AWS-to-on-premises connectivity with WireGuard HA, active-standby failover, and deep packet-forwarding observability.
We Built a 114-Second AWS-to-Azure Failover. Here’s What We Learned
Technology

Jun 19, 2026

We Built a 114-Second AWS-to-Azure Failover. Here’s What We Learned
A practical guide to building a 114-second multi-cloud disaster recovery failover between AWS and Azure — what we built, what broke, and what we learned.
Cloud-Native and Cloud-Agnostic Are Not Ideologies; They Are Business-Stage Decisions
Technology

Jun 12, 2026

Cloud-Native and Cloud-Agnostic Are Not Ideologies; They Are Business-Stage Decisions
This blog explains how organizations can balance speed, scalability, and operational flexibility as they grow from startup to enterprise scale.

The Right Conversation Can Save You Six Months.

Whether you’re navigating AI adoption, modernizing legacy systems, or scaling a product - we start by listening. No pitch deck. No template. A real conversation.