How to mock dependencies in Node.js tests

Mocking dependencies in Node.js tests isolates the code under test from external services, databases, and APIs for fast, reliable unit tests. As the creator of CoreUI with over 12 years of Node.js experience since 2014, I’ve written comprehensive test suites with extensive mocking. Jest provides powerful mocking capabilities including module mocks, function mocks, and spies that work seamlessly with Node.js. This approach creates independent tests that run quickly without external dependencies.

Use Jest mocking to isolate units under test by mocking modules, functions, and external dependencies.

Mocking modules:

// database.js
class Database {
  async findUser(id) {
    // Real database query
    return await this.query('SELECT * FROM users WHERE id = ?', [id])
  }

  async createUser(userData) {
    // Real database insert
    return await this.query('INSERT INTO users SET ?', userData)
  }
}

module.exports = Database

// userService.js
const Database = require('./database')

class UserService {
  constructor() {
    this.db = new Database()
  }

  async getUser(id) {
    const user = await this.db.findUser(id)
    if (!user) {
      throw new Error('User not found')
    }
    return user
  }

  async createUser(userData) {
    if (!userData.email) {
      throw new Error('Email required')
    }
    return await this.db.createUser(userData)
  }
}

module.exports = UserService

// userService.test.js
const UserService = require('./userService')
const Database = require('./database')

jest.mock('./database')

describe('UserService', () => {
  let service
  let mockDb

  beforeEach(() => {
    mockDb = {
      findUser: jest.fn(),
      createUser: jest.fn()
    }
    Database.mockImplementation(() => mockDb)
    service = new UserService()
  })

  afterEach(() => {
    jest.clearAllMocks()
  })

  it('gets user successfully', async () => {
    const mockUser = { id: 1, name: 'John', email: '[email protected]' }
    mockDb.findUser.mockResolvedValue(mockUser)

    const result = await service.getUser(1)

    expect(result).toEqual(mockUser)
    expect(mockDb.findUser).toHaveBeenCalledWith(1)
  })

  it('throws error when user not found', async () => {
    mockDb.findUser.mockResolvedValue(null)

    await expect(service.getUser(1)).rejects.toThrow('User not found')
  })

  it('creates user with valid data', async () => {
    const userData = { name: 'John', email: '[email protected]' }
    const mockCreated = { id: 1, ...userData }
    mockDb.createUser.mockResolvedValue(mockCreated)

    const result = await service.createUser(userData)

    expect(result).toEqual(mockCreated)
    expect(mockDb.createUser).toHaveBeenCalledWith(userData)
  })
})

Mocking Node.js built-in modules:

// fileService.js
const fs = require('fs').promises

class FileService {
  async readConfig() {
    const data = await fs.readFile('config.json', 'utf8')
    return JSON.parse(data)
  }

  async writeConfig(config) {
    await fs.writeFile('config.json', JSON.stringify(config, null, 2))
  }
}

module.exports = FileService

// fileService.test.js
const FileService = require('./fileService')
const fs = require('fs').promises

jest.mock('fs', () => ({
  promises: {
    readFile: jest.fn(),
    writeFile: jest.fn()
  }
}))

describe('FileService', () => {
  let service

  beforeEach(() => {
    service = new FileService()
    jest.clearAllMocks()
  })

  it('reads config from file', async () => {
    const mockConfig = { setting: 'value' }
    fs.readFile.mockResolvedValue(JSON.stringify(mockConfig))

    const result = await service.readConfig()

    expect(result).toEqual(mockConfig)
    expect(fs.readFile).toHaveBeenCalledWith('config.json', 'utf8')
  })

  it('writes config to file', async () => {
    const config = { setting: 'new-value' }
    fs.writeFile.mockResolvedValue(undefined)

    await service.writeConfig(config)

    expect(fs.writeFile).toHaveBeenCalledWith(
      'config.json',
      JSON.stringify(config, null, 2)
    )
  })
})

Mocking HTTP requests:

// apiClient.js
const axios = require('axios')

class ApiClient {
  async fetchUsers() {
    const response = await axios.get('https://api.example.com/users')
    return response.data
  }

  async createUser(userData) {
    const response = await axios.post('https://api.example.com/users', userData)
    return response.data
  }
}

module.exports = ApiClient

// apiClient.test.js
const ApiClient = require('./apiClient')
const axios = require('axios')

jest.mock('axios')

describe('ApiClient', () => {
  let client

  beforeEach(() => {
    client = new ApiClient()
    jest.clearAllMocks()
  })

  it('fetches users from API', async () => {
    const mockUsers = [{ id: 1, name: 'John' }]
    axios.get.mockResolvedValue({ data: mockUsers })

    const result = await client.fetchUsers()

    expect(result).toEqual(mockUsers)
    expect(axios.get).toHaveBeenCalledWith('https://api.example.com/users')
  })

  it('creates user via API', async () => {
    const userData = { name: 'Jane' }
    const mockResponse = { id: 2, ...userData }
    axios.post.mockResolvedValue({ data: mockResponse })

    const result = await client.createUser(userData)

    expect(result).toEqual(mockResponse)
    expect(axios.post).toHaveBeenCalledWith(
      'https://api.example.com/users',
      userData
    )
  })

  it('handles API errors', async () => {
    axios.get.mockRejectedValue(new Error('Network error'))

    await expect(client.fetchUsers()).rejects.toThrow('Network error')
  })
})

Partial mocking:

// mathUtils.js
const add = (a, b) => a + b
const subtract = (a, b) => a - b
const multiply = (a, b) => a * b

module.exports = { add, subtract, multiply }

// calculator.js
const { add, multiply } = require('./mathUtils')

class Calculator {
  calculate(a, b) {
    const sum = add(a, b)
    return multiply(sum, 2)
  }
}

module.exports = Calculator

// calculator.test.js
const Calculator = require('./calculator')
const mathUtils = require('./mathUtils')

jest.mock('./mathUtils', () => ({
  ...jest.requireActual('./mathUtils'),
  multiply: jest.fn()
}))

describe('Calculator', () => {
  it('uses real add and mocked multiply', () => {
    mathUtils.multiply.mockReturnValue(100)

    const calculator = new Calculator()
    const result = calculator.calculate(5, 10)

    // add is real: 5 + 10 = 15
    // multiply is mocked: returns 100
    expect(result).toBe(100)
    expect(mathUtils.multiply).toHaveBeenCalledWith(15, 2)
  })
})

Dependency injection for easier mocking:

// emailService.js
class EmailService {
  constructor(mailer) {
    this.mailer = mailer
  }

  async sendWelcomeEmail(user) {
    return await this.mailer.send({
      to: user.email,
      subject: 'Welcome',
      body: `Hello ${user.name}`
    })
  }
}

module.exports = EmailService

// emailService.test.js
const EmailService = require('./emailService')

describe('EmailService', () => {
  it('sends welcome email', async () => {
    const mockMailer = {
      send: jest.fn().mockResolvedValue({ success: true })
    }

    const service = new EmailService(mockMailer)
    const user = { name: 'John', email: '[email protected]' }

    await service.sendWelcomeEmail(user)

    expect(mockMailer.send).toHaveBeenCalledWith({
      to: '[email protected]',
      subject: 'Welcome',
      body: 'Hello John'
    })
  })
})

Spying on methods:

const logger = {
  info: (msg) => console.log(msg),
  error: (msg) => console.error(msg)
}

describe('Logger spy', () => {
  it('logs messages', () => {
    const infoSpy = jest.spyOn(logger, 'info')

    logger.info('Test message')

    expect(infoSpy).toHaveBeenCalledWith('Test message')
    expect(infoSpy).toHaveBeenCalledTimes(1)

    infoSpy.mockRestore()
  })
})

Mocking timers:

// scheduler.js
class Scheduler {
  scheduleTask(callback, delay) {
    setTimeout(callback, delay)
  }

  scheduleInterval(callback, interval) {
    return setInterval(callback, interval)
  }
}

module.exports = Scheduler

// scheduler.test.js
const Scheduler = require('./scheduler')

jest.useFakeTimers()

describe('Scheduler', () => {
  let scheduler

  beforeEach(() => {
    scheduler = new Scheduler()
  })

  it('executes task after delay', () => {
    const callback = jest.fn()

    scheduler.scheduleTask(callback, 1000)

    expect(callback).not.toHaveBeenCalled()

    jest.advanceTimersByTime(1000)

    expect(callback).toHaveBeenCalledTimes(1)
  })

  it('executes interval tasks', () => {
    const callback = jest.fn()

    scheduler.scheduleInterval(callback, 500)

    jest.advanceTimersByTime(1500)

    expect(callback).toHaveBeenCalledTimes(3)
  })
})

Best Practice Note

Use jest.mock() to mock entire modules automatically. Mock external dependencies like databases and APIs to keep tests fast and independent. Use dependency injection to make mocking easier—pass dependencies as constructor arguments. Spy on real implementations when you need to verify calls without changing behavior. Use jest.clearAllMocks() in afterEach to reset mocks between tests. Mock timers with jest.useFakeTimers() for testing time-dependent code. This is how we mock dependencies in CoreUI Node.js tests—ensuring isolated, fast, reliable unit tests that don’t depend on external services or databases.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team