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

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
Subscribe to RSS
Press & Media Hub RSS FeedRELATED ARTICLES







