How to Mock Dependencies in Node.js Unit Tests
Unit testing in Node.js requires isolating your application logic from external factors like databases, APIs, and file systems. This article provides a straightforward guide on how to mock dependencies in Node.js using popular testing tools, including Jest, Sinon.js, and the native Node.js test runner, helping you write fast, reliable, and predictable unit tests.
Why Mock Dependencies?
Mocking replaces real dependencies with controlled, dummy implementations. This ensures your tests run quickly, do not make actual network requests, and consistently produce the same results regardless of external state. By isolating the module under test, you can verify how your code handles specific inputs, successful responses, and unexpected errors.
Mocking with Jest
Jest is the most popular testing framework in the Node.js ecosystem, offering robust, built-in mocking capabilities.
To mock an external module (such as axios for HTTP
requests), use jest.mock().
// userService.js
const axios = require('axios');
async function getUser(id) {
const response = await axios.get(`https://api.example.com/users/${id}`);
return response.data;
}
module.exports = { getUser };// userService.test.js
const axios = require('axios');
const { getUser } = require('./userService');
// Mock the entire axios module
jest.mock('axios');
test('should fetch and return user data', async () => {
// Configure the mock response
axios.get.mockResolvedValue({ data: { id: 1, name: 'John Doe' } });
const user = await getUser(1);
expect(user.name).toBe('John Doe');
expect(axios.get).toHaveBeenCalledWith('https://api.example.com/users/1');
});Mocking with Sinon.js
If you use testing frameworks like Mocha or Jasmine, Sinon.js is the industry standard for creating stubs, spies, and mocks. Stubs are highly effective for replacing database queries or third-party service calls.
// database.js
module.exports = {
findUser: async (id) => { /* Real DB Call */ }
};// authService.js
const db = require('./database');
async function authenticate(id) {
const user = await db.findUser(id);
return user ? true : false;
}
module.exports = { authenticate };// authService.test.js
const sinon = require('sinon');
const assert = require('assert');
const db = require('./database');
const { authenticate } = require('./authService');
describe('Authentication Service', () => {
let dbStub;
beforeEach(() => {
// Stub the findUser method before each test
dbStub = sinon.stub(db, 'findUser');
});
afterEach(() => {
// Restore the original method after each test
dbStub.restore();
});
it('should return true if the user exists', async () => {
dbStub.resolves({ id: 1, name: 'Alice' });
const result = await authenticate(1);
assert.strictEqual(result, true);
assert.strictEqual(dbStub.calledOnceWith(1), true);
});
});Mocking with Node.js Native Test Runner
Modern versions of Node.js (v18+) include a built-in test runner with
basic mocking utilities available via the node:test
module.
import { test, mock } from 'node:test';
import assert from 'node:assert';
const paymentGateway = {
charge: async (amount) => { /* Real payment call */ }
};
test('should mock a method using native Node.js testing tools', async () => {
// Mock the charge method
const chargeMock = mock.method(paymentGateway, 'charge', async (amount) => {
return { success: true, transactionId: '12345' };
});
const response = await paymentGateway.charge(100);
assert.strictEqual(response.success, true);
assert.strictEqual(chargeMock.mock.calls.length, 1);
assert.strictEqual(chargeMock.mock.calls[0].arguments[0], 100);
// Restore the original method
mock.reset();
});Best Practices for Mocking
- Reset mocks between tests: Always clear or restore
mocks (using
jest.clearAllMocks(),sinon.restore(), ormock.reset()) to prevent test pollution where one test affects another. - Mock at the boundaries: Only mock external dependencies, databases, or complex network layers. Avoid mocking internal helper functions that are part of the unit under test.
- Keep mocks realistic: Ensure your mocked return values match the data shapes and types of the real APIs to avoid passing tests that would fail in production.