Jest Mock: The Complete Guide to Mocking in Jest

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:

  1. Assert the function was called (spy behavior)
  2. 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.

Read more