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.

This article was originally published on the GeekyAnts Blog. Author: Nilesh Kumar, Software Engineer II at GeekyAnts.
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');
// Mock a GET request
nock('https://api.example.com')
.get('/users/1')
.reply(200, {
id: 1,
name: 'John Doe',
email: 'john@example.com'
});
// Mock a POST request with request body matching
nock('https://api.example.com')
.post('/users', { name: 'Jane Doe', email: 'jane@example.com' })
.reply(201, { id: 2, name: 'Jane Doe' });
// Mock with headers
nock('https://api.example.com')
.get('/protected-resource')
.matchHeader('Authorization', 'Bearer my-token')
.reply(200, { data: 'secret' });
// Mock an error response
nock('https://api.example.com')
.get('/flaky-endpoint')
.replyWithError('Connection refused');
// Clean up after tests
afterEach(() => {
nock.cleanAll();
});
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');
describe('UserService', () => {
let sandbox;
beforeEach(() => {
sandbox = sinon.createSandbox();
});
afterEach(() => {
sandbox.restore();
});
it('should call the email service on registration', async () => {
// Create a spy
const emailSpy = sandbox.spy(emailService, 'sendWelcomeEmail');
await userService.register({ name: 'Alice', email: 'alice@example.com' });
sinon.assert.calledOnce(emailSpy);
sinon.assert.calledWith(emailSpy, 'alice@example.com');
});
it('should handle email service failure gracefully', async () => {
// Stub to simulate failure
sandbox.stub(emailService, 'sendWelcomeEmail').rejects(new Error('SMTP error'));
const result = await userService.register({ name: 'Bob', email: 'bob@example.com' });
expect(result.status).toBe('registered_without_email');
});
it('should expire tokens after timeout', async () => {
const clock = sandbox.useFakeTimers();
const token = await authService.createToken('user-123');
clock.tick(3600 * 1000); // Advance 1 hour
const isValid = await authService.validateToken(token);
expect(isValid).toBe(false);
clock.restore();
});
});
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:
// Auto-mock an entire module
jest.mock('./emailService');
// Manual mock with implementation
jest.mock('./paymentGateway', () => ({
charge: jest.fn().mockResolvedValue({ success: true, transactionId: 'txn_123' }),
refund: jest.fn().mockResolvedValue({ success: true }),
}));
describe('OrderService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should process payment on order placement', async () => {
const { charge } = require('./paymentGateway');
await orderService.placeOrder({ userId: 1, items: [...], total: 99.99 });
expect(charge).toHaveBeenCalledWith({
amount: 99.99,
currency: 'USD',
userId: 1,
});
});
it('should handle payment failure', async () => {
const { charge } = require('./paymentGateway');
charge.mockRejectedValueOnce(new Error('Card declined'));
await expect(
orderService.placeOrder({ userId: 1, items: [...], total: 99.99 })
).rejects.toThrow('Payment failed');
});
});
Step-by-Step Implementation Guide
Let's walk through a comprehensive example implementing integration tests with strategic mocking for a user notification system.
Application Architecture
Our example notification system includes:
NotificationService — orchestrates sending notifications
EmailProvider — external SMTP/email API
SMSProvider — external SMS gateway
UserRepository — database layer for user data
NotificationLog — records sent notifications
Step 1: Test Environment Setup
// test/setup.js
const nock = require('nock');
const { MongoMemoryServer } = require('mongodb-memory-server');
let mongoServer;
beforeAll(async () => {
// Spin up in-memory MongoDB
mongoServer = await MongoMemoryServer.create();
process.env.MONGO_URI = mongoServer.getUri();
// Disable real HTTP calls during tests
nock.disableNetConnect();
nock.enableNetConnect('127.0.0.1'); // allow localhost
});
afterAll(async () => {
await mongoServer.stop();
nock.enableNetConnect();
});
afterEach(() => {
nock.cleanAll();
});
Step 2: Basic Integration Test with Mocking
// test/notification.integration.test.js
const request = require('supertest');
const nock = require('nock');
const app = require('../src/app');
const { seedUser } = require('./helpers/seed');
describe('POST /notifications/send', () => {
let user;
beforeEach(async () => {
user = await seedUser({ email: 'user@example.com', phone: '+1234567890' });
});
it('should send email and SMS notifications successfully', async () => {
// Mock email provider
nock('https://api.sendgrid.com')
.post('/v3/mail/send')
.reply(202, { message: 'Accepted' });
// Mock SMS provider
nock('https://api.twilio.com')
.post(`/2010-04-01/Accounts/${process.env.TWILIO_SID}/Messages.json`)
.reply(201, { sid: 'SM123', status: 'queued' });
const response = await request(app)
.post('/notifications/send')
.send({ userId: user.id, message: 'Your order has shipped!' })
.set('Authorization', `Bearer ${process.env.TEST_API_KEY}`);
expect(response.status).toBe(200);
expect(response.body).toMatchObject({
email: { status: 'sent' },
sms: { status: 'queued' },
});
});
it('should still send SMS if email provider fails', async () => {
nock('https://api.sendgrid.com')
.post('/v3/mail/send')
.reply(500, { error: 'Internal Server Error' });
nock('https://api.twilio.com')
.post(`/2010-04-01/Accounts/${process.env.TWILIO_SID}/Messages.json`)
.reply(201, { sid: 'SM456', status: 'queued' });
const response = await request(app)
.post('/notifications/send')
.send({ userId: user.id, message: 'Your order has shipped!' })
.set('Authorization', `Bearer ${process.env.TEST_API_KEY}`);
expect(response.status).toBe(207); // Partial success
expect(response.body.email.status).toBe('failed');
expect(response.body.sms.status).toBe('queued');
});
});
Step 3: Advanced Mocking Scenarios
Dynamic Response Generation:
// Respond differently based on request body
nock('https://api.sendgrid.com')
.post('/v3/mail/send', (body) => body.to === 'vip@example.com')
.reply(202, { priority: 'high' });
nock('https://api.sendgrid.com')
.post('/v3/mail/send')
.reply(202, { priority: 'normal' });
Simulating Rate Limits:
// First 3 calls succeed, then rate limit kicks in
nock('https://api.sendgrid.com')
.post('/v3/mail/send')
.times(3)
.reply(202)
.post('/v3/mail/send')
.reply(429, { error: 'Too Many Requests' });
Conditional Mocking Based on Environment:
const mockExternalServices = () => {
if (process.env.NODE_ENV === 'test') {
nock('https://api.sendgrid.com')
.persist()
.post('/v3/mail/send')
.reply(202);
nock('https://api.twilio.com')
.persist()
.post(/Messages\.json$/)
.reply(201, { status: 'queued' });
}
};
Step 4: Performance Testing with Mocks
it('should handle 100 concurrent notification requests', async () => {
nock('https://api.sendgrid.com')
.post('/v3/mail/send')
.times(100)
.reply(202);
nock('https://api.twilio.com')
.post(/Messages\.json$/)
.times(100)
.reply(201, { status: 'queued' });
const requests = Array.from({ length: 100 }, (_, i) =>
request(app)
.post('/notifications/send')
.send({ userId: `user-${i}`, message: 'Test notification' })
.set('Authorization', `Bearer ${process.env.TEST_API_KEY}`)
);
const start = Date.now();
const responses = await Promise.all(requests);
const duration = Date.now() - start;
expect(responses.every((r) => r.status === 200)).toBe(true);
expect(duration).toBeLessThan(5000); // Should complete in under 5 seconds
});
Advanced Mocking Techniques
Request Recording and Playback
Use Nock's record mode to capture real API responses and replay them in tests — great for staying in sync with real API behaviour without hitting production systems on every run.
// Record mode (run once against real APIs)
if (process.env.RECORD_MOCKS === 'true') {
nock.recorder.rec({ output_objects: true });
}
// Playback mode (default)
const recordings = require('./fixtures/api-recordings.json');
nock.define(recordings);
Mock Validation and Maintenance
Always assert that all registered mocks were actually called — this catches dead code and stale mocks early:
afterEach(() => {
// Verify all registered nock interceptors were used
expect(nock.pendingMocks()).toHaveLength(0);
nock.cleanAll();
});
Best Practices for Mock Implementation
1. Keep Mocks Simple and Focused Each mock should represent exactly one scenario. Avoid overloading a single mock with conditional logic — create separate test cases instead.
2. Use Realistic Data and Delays Make your mocks representative of real system behaviour. Add response delays where appropriate and use production-like payloads:
nock('https://api.sendgrid.com')
.post('/v3/mail/send')
.delay(120) // Simulate realistic network latency
.reply(202, { message: 'Accepted' });
3. Mock at the Appropriate Level
Use Nock for external HTTP services
Use Sinon stubs for internal module dependencies
Use in-memory databases rather than mocking the DB layer
4. Document Mock Decisions Leave comments explaining why a mock was introduced, what real behaviour it replaces, and when it should be revisited:
// Mocking SendGrid because the free tier has strict rate limits
// in CI. Review if we upgrade to a paid plan — real integration
// tests would be preferable.
nock('https://api.sendgrid.com')
.post('/v3/mail/send')
.reply(202);
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.
By following the patterns and best practices in this guide, you can create integration tests that give you genuine confidence in your system's behaviour while remaining practical to execute and maintain.
Remember: 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 your application evolves.
Originally published on GeekyAnts Blog by Nilesh Kumar.





