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