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.



