Jest Mock: The Complete Guide to Mocking in Jest
Jest's mocking system is one of its strongest features. The three tools you need to know: jest.fn() for mock functions, jest.mock() for mocking entire modules, and jest.spyOn() for watching real implementations. Everything else builds on these three.
Key Takeaways
jest.fn() creates a mock function that records all calls. You can configure what it returns, inspect what arguments it was called with, and assert how many times it ran.
jest.mock('moduleName') replaces an entire module with mocks. All exports become jest.fn(). Essential for mocking database clients, HTTP clients, and file system access.
jest.spyOn() wraps a real function to observe calls without replacing it. Unlike jest.mock(), the original implementation still runs. Useful for asserting side effects without breaking real behavior.
Always clear mocks between tests. Use jest.clearAllMocks() in afterEach or set clearMocks: true in your Jest config to prevent state leaking between tests.
mockResolvedValue() is the async version of mockReturnValue(). For async functions and Promises, use mockResolvedValue (success) and mockRejectedValue (failure).
Mocking is the practice of replacing real dependencies (databases, APIs, file systems, timers) with controlled substitutes during testing. Jest has built-in mocking capabilities that cover every common use case. Here is how to use them.
jest.fn(): Mock Functions
jest.fn() creates a mock function — a function that records its calls and can be configured to return specific values.
Basic Usage
const mockCallback = jest.fn()
// Call it
mockCallback('hello', 'world')
mockCallback('second call')
// Assert on calls
expect(mockCallback).toHaveBeenCalledTimes(2)
expect(mockCallback).toHaveBeenCalledWith('hello', 'world')
expect(mockCallback).toHaveBeenLastCalledWith('second call')
Configuring Return Values
// Return a fixed value
const mockFn = jest.fn().mockReturnValue(42)
expect(mockFn()).toBe(42)
expect(mockFn()).toBe(42) // same value every time
// Return different values on successive calls
const mockFn = jest.fn()
.mockReturnValueOnce('first')
.mockReturnValueOnce('second')
.mockReturnValue('default') // all subsequent calls
expect(mockFn()).toBe('first')
expect(mockFn()).toBe('second')
expect(mockFn()).toBe('default')
expect(mockFn()).toBe('default')
Async Return Values
// Return a resolved Promise
const mockFetch = jest.fn().mockResolvedValue({ data: 'response' })
// In an async test
const result = await mockFetch('https://api.example.com')
expect(result).toEqual({ data: 'response' })
// Return a rejected Promise (simulate API error)
const mockFetch = jest.fn().mockRejectedValue(new Error('Network error'))
await expect(mockFetch()).rejects.toThrow('Network error')
// Different values for different calls
const mockApi = jest.fn()
.mockResolvedValueOnce({ user: { id: 1 } })
.mockRejectedValueOnce(new Error('Server error'))
Mock Function Implementation
// Provide a custom implementation
const mockFn = jest.fn((x, y) => x + y)
expect(mockFn(2, 3)).toBe(5)
// Change implementation later
mockFn.mockImplementation((x) => x * 2)
expect(mockFn(5)).toBe(10)
// Single-use implementation
mockFn.mockImplementationOnce(() => 'special case')
Inspecting Calls
const mockFn = jest.fn()
mockFn('a', 1)
mockFn('b', 2)
// Full call history
console.log(mockFn.mock.calls)
// [['a', 1], ['b', 2]]
// All return values
console.log(mockFn.mock.results)
// [{ type: 'return', value: undefined }, ...]
// Number of calls
console.log(mockFn.mock.calls.length) // 2
jest.mock(): Module Mocking
jest.mock('moduleName') replaces an entire module. All exports become mock functions. This is how you prevent tests from hitting real databases, APIs, or the file system.
Auto-Mocking a Module
// Automatically replace all exports with jest.fn()
jest.mock('./database')
// Now database.query is a jest.fn()
import { query } from './database'
// Configure what it returns
query.mockResolvedValue([{ id: 1, name: 'Alice' }])
Manual Mock Implementation
jest.mock('./emailService', () => ({
sendEmail: jest.fn().mockResolvedValue({ success: true }),
sendBulkEmail: jest.fn().mockResolvedValue({ sent: 100 }),
}))
Mocking Third-Party Modules
// Mock axios
jest.mock('axios')
import axios from 'axios'
axios.get.mockResolvedValue({
data: { users: [{ id: 1 }] },
status: 200,
})
// In your test:
const result = await fetchUsers()
expect(axios.get).toHaveBeenCalledWith('/api/users')
Mocking Node.js Built-ins
// Mock the fs module
jest.mock('fs', () => ({
readFileSync: jest.fn().mockReturnValue('file contents'),
writeFileSync: jest.fn(),
existsSync: jest.fn().mockReturnValue(true),
}))
import fs from 'fs'
it('reads config file', () => {
const config = loadConfig('./config.json')
expect(fs.readFileSync).toHaveBeenCalledWith('./config.json', 'utf8')
})
Partial Module Mock
Sometimes you want to mock only some exports while keeping others real:
jest.mock('./utils', () => ({
...jest.requireActual('./utils'), // keep real implementations
formatDate: jest.fn().mockReturnValue('2026-01-01'), // mock only this
}))
Resetting Mocks Between Tests
afterEach(() => {
jest.clearAllMocks() // clear call history and return values
})
// Or reset to initial mock implementation:
afterEach(() => {
jest.resetAllMocks()
})
// Or restore original implementations:
afterEach(() => {
jest.restoreAllMocks()
})
| Method | What it does |
|---|---|
clearAllMocks() |
Clears .mock.calls, .mock.results, .mock.instances |
resetAllMocks() |
Also removes mock implementations (mockReturnValue, etc.) |
restoreAllMocks() |
Restores original implementations (for spies only) |
jest.spyOn(): Watching Real Functions
jest.spyOn() wraps an existing method to observe calls without replacing the implementation. The original code still runs — you're just adding recording.
Basic Spy
const user = {
greet(name) {
return `Hello, ${name}!`
}
}
const spy = jest.spyOn(user, 'greet')
const result = user.greet('Alice')
// Original implementation ran: result === 'Hello, Alice!'
expect(spy).toHaveBeenCalledWith('Alice')
expect(spy).toHaveBeenCalledTimes(1)
spy.mockRestore() // restore original implementation
Spying on Module Methods
import * as dateUtils from './dateUtils'
it('calls formatDate with the right args', () => {
const spy = jest.spyOn(dateUtils, 'formatDate')
processEvent({ date: new Date('2026-01-15') })
expect(spy).toHaveBeenCalledWith(new Date('2026-01-15'), 'YYYY-MM-DD')
})
Spy + Mock Implementation
const spy = jest.spyOn(api, 'getUser').mockResolvedValue({
id: 1,
name: 'Test User'
})
This is useful when you want to both:
- Assert the function was called (spy behavior)
- Control what it returns (mock behavior)
Spying on Console
it('logs a warning for invalid input', () => {
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
processInput(null)
expect(consoleSpy).toHaveBeenCalledWith('Invalid input: null')
consoleSpy.mockRestore()
})
Common Mocking Patterns
Mocking a Database Client
// __mocks__/db.js (manual mock file)
const db = {
query: jest.fn(),
connect: jest.fn().mockResolvedValue(true),
disconnect: jest.fn().mockResolvedValue(true),
}
module.exports = db
// In your test:
jest.mock('../db')
import db from '../db'
describe('UserService', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('returns user from database', async () => {
db.query.mockResolvedValue([{ id: 1, email: 'user@example.com' }])
const user = await UserService.findById(1)
expect(db.query).toHaveBeenCalledWith(
'SELECT * FROM users WHERE id = ?',
[1]
)
expect(user.email).toBe('user@example.com')
})
it('handles database errors', async () => {
db.query.mockRejectedValue(new Error('Connection timeout'))
await expect(UserService.findById(1)).rejects.toThrow('Connection timeout')
})
})
Mocking fetch / HTTP Calls
// Global fetch mock
global.fetch = jest.fn()
describe('API client', () => {
afterEach(() => {
jest.clearAllMocks()
})
it('fetches users', async () => {
fetch.mockResolvedValue({
ok: true,
status: 200,
json: () => Promise.resolve([{ id: 1, name: 'Alice' }]),
})
const users = await getUsers()
expect(fetch).toHaveBeenCalledWith('/api/users', expect.objectContaining({
method: 'GET',
}))
expect(users).toHaveLength(1)
})
it('handles network failures', async () => {
fetch.mockRejectedValue(new Error('Failed to fetch'))
await expect(getUsers()).rejects.toThrow('Failed to fetch')
})
})
Mocking Timers
describe('debounce', () => {
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.useRealTimers()
})
it('delays execution', () => {
const callback = jest.fn()
const debouncedFn = debounce(callback, 500)
debouncedFn()
expect(callback).not.toHaveBeenCalled()
jest.advanceTimersByTime(500)
expect(callback).toHaveBeenCalledTimes(1)
})
it('cancels previous calls', () => {
const callback = jest.fn()
const debouncedFn = debounce(callback, 500)
debouncedFn()
debouncedFn()
debouncedFn()
jest.advanceTimersByTime(500)
expect(callback).toHaveBeenCalledTimes(1) // only once
})
})
Mocking Environment Variables
describe('Config loader', () => {
const originalEnv = process.env
beforeEach(() => {
jest.resetModules()
process.env = { ...originalEnv }
})
afterAll(() => {
process.env = originalEnv
})
it('loads API key from environment', () => {
process.env.API_KEY = 'test-api-key-123'
const config = require('./config')
expect(config.apiKey).toBe('test-api-key-123')
})
it('throws when API key is missing', () => {
delete process.env.API_KEY
expect(() => require('./config')).toThrow('API_KEY is required')
})
})
Manual Mocks with __mocks__ Directory
For modules you mock frequently, create a __mocks__ directory.
project/
__mocks__/
axios.js # auto-used when jest.mock('axios') is called
./db.js # mock for relative import ../db
src/
services/
user.js
__mocks__/axios.js:
const axios = {
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
delete: jest.fn(),
create: jest.fn(() => axios),
}
module.exports = axios
With this file in place, jest.mock('axios') automatically uses your manual mock.
Jest Configuration for Mocking
jest.config.js:
export default {
// Clear mock.calls and mock.instances between tests
clearMocks: true,
// Reset mockReturnValue and mockImplementation between tests
resetMocks: false,
// Restore jest.spyOn implementations between tests
restoreMocks: true,
// Auto-mock all modules (aggressive — usually don't want this)
automock: false,
}
Recommended starting configuration: clearMocks: true and restoreMocks: true. This prevents test pollution without being so aggressive that you need to re-configure mocks in every test.
Common Mistakes
Forgetting to clear mocks:
// WRONG: mock state from test 1 leaks into test 2
it('first test', () => {
mockFn.mockReturnValue('a')
// ...
})
it('second test', () => {
// mockFn still returns 'a' from test 1!
// ...
})
// CORRECT: clear between tests
afterEach(() => { jest.clearAllMocks() })
Not restoring spies:
// WRONG: spy persists after test
it('test', () => {
jest.spyOn(console, 'log')
// ...
// never restored!
})
// CORRECT
afterEach(() => { jest.restoreAllMocks() })
Mocking too much: Mocking every dependency makes tests brittle. Prefer testing real integrations when they are fast and deterministic. Mock when:
- The dependency is slow (real database, external API)
- The dependency has side effects (sending emails, writing files)
- You need to test error handling for unreliable behavior
Summary
| Tool | Use When |
|---|---|
jest.fn() |
Create a standalone mock function for callbacks and injected dependencies |
jest.mock('module') |
Replace an entire module (database, HTTP client, filesystem) |
jest.spyOn(obj, 'method') |
Observe calls to a real method without replacing its implementation |
mockReturnValue |
Control synchronous return values |
mockResolvedValue |
Control async/Promise return values |
mockImplementation |
Provide custom logic for a mock function |
jest.useFakeTimers() |
Control setTimeout, setInterval, Date |
clearAllMocks() |
Clear call history (keep implementations) |
resetAllMocks() |
Clear call history and implementations |
restoreAllMocks() |
Restore spied original implementations |
Mocking is about making tests fast, deterministic, and focused on the unit under test. Master these tools, configure clearMocks: true in your Jest config, and your test suite will be reliable and maintainable.