Geeks logo

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.

By GeekyantsPublished 4 months ago • 6 min read

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: '[email protected]'

});

// Mock a POST request with body matching

nock('https://api.example.com')

.post('/users', { name: 'Jane Doe', email: '[email protected]' })

.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 () => {

const emailSpy = sandbox.spy(emailService, 'sendWelcomeEmail');

await userService.register({ name: 'Alice', email: '[email protected]' });

sinon.assert.calledOnce(emailSpy);

sinon.assert.calledWith(emailSpy, '[email protected]');

});

it('should handle email service failure gracefully', async () => {

sandbox.stub(emailService, 'sendWelcomeEmail').rejects(new Error('SMTP error'));

const result = await userService.register({ name: 'Bob', email: '[email protected]' });

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:

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, 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, 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. Our example includes a NotificationService that orchestrates email and SMS delivery via external providers, a UserRepository for database access, and a NotificationLog for audit records.

Step 1: Test Environment Setup

// test/setup.js

const nock = require('nock');

const { MongoMemoryServer } = require('mongodb-memory-server');

let mongoServer;

beforeAll(async () => {

mongoServer = await MongoMemoryServer.create();

process.env.MONGO_URI = mongoServer.getUri();

nock.disableNetConnect();

nock.enableNetConnect('127.0.0.1');

});

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: '[email protected]', phone: '+1234567890' });

});

it('should send email and SMS notifications successfully', async () => {

nock('https://api.sendgrid.com')

.post('/v3/mail/send')

.reply(202, { message: 'Accepted' });

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);

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 content:

nock('https://api.sendgrid.com')

.post('/v3/mail/send', (body) => body.to === '[email protected]')

.reply(202, { priority: 'high' });

nock('https://api.sendgrid.com')

.post('/v3/mail/send')

.reply(202, { priority: 'normal' });

Simulating Rate Limits:

nock('https://api.sendgrid.com')

.post('/v3/mail/send')

.times(3)

.reply(202)

.post('/v3/mail/send')

.reply(429, { error: 'Too Many Requests' });

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);

});

ADVANCED MOCKING TECHNIQUES

Request Recording and Playback

Use Nock's record mode to capture real API responses once and replay them in all future test runs:

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

Always verify that all registered mocks were called — this surfaces stale or unused mocks:

afterEach(() => {

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 embedding complex conditional logic inside a mock — create separate test cases instead.

2. Use Realistic Data and Delays

Make mocks representative of real system behavior. Add simulated latency where it matters:

nock('https://api.sendgrid.com')

.post('/v3/mail/send')

.delay(120)

.reply(202);

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 database layer

4. Document Mock Decisions

Always leave a comment explaining why a mock was introduced and when it should be revisited:

// Mocking SendGrid because CI runs against the free tier which has strict

// rate limits. Revisit if we move 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 — eliminating 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 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.

Remember: mocks are tools to enable effective testing, not goals in themselves. Regularly review and update your mocking strategies as your application evolves to ensure they continue to accurately represent the systems they replace.

---

Originally published on GeekyAnts Blog: https://geekyants.com/blog/how-to-mock-in-integration-tests-tools-and-implementation

Author: Nilesh Kumar, Software Engineer II at GeekyAnts (https://geekyants.com)

how to

About the Creator

Geekyants

GeekyAnts is a global technology consulting and product development company specializing in digital transformation, custom software solutions, and end-to-end app development.

Enjoyed the story? Support the Creator.

Subscribe for free to receive all their stories in your feed.

Subscribe For Free

Reader insights

Comments

There are no comments for this story

Be the first to respond and start the conversation.

Sign in to comment
    Written by Geekyants