पाठ 10 / 25

Mock Functions and Spies

jest.fn / vi.fn and spyOn.

Record calls and control return values

A mock function (jest.fn() / vi.fn()) records every call, its arguments and its return values, and lets you set behaviour with mockReturnValue, mockResolvedValue, mockRejectedValue or mockImplementation. Use it for callbacks and injected dependencies, then assert with toHaveBeenCalled, toHaveBeenCalledWith and toHaveBeenCalledTimes. A spy (jest.spyOn(obj, 'method') / vi.spyOn) wraps an existing method on a real object: by default it still calls the original, but you can override it. Know the reset family: mockClear clears recorded calls, mockReset also removes implementations, and mockRestore puts the original method back for spies (exact semantics have shifted between versions, so check the docs for yours).

Replace the parts you do not want to test

Mocks stand in for collaborators so a test can focus on one piece of behaviour, but every mock is an assumption about the real thing.

Three ideas: mock functions and spies, module mocks, network mocks with MSW.
Figure 4.1 — From single functions, to whole modules, to the network boundary.

A callback mock and a console spy

Vitest; swap vi for jest in Jest.

import { afterEach, expect, it, vi } from 'vitest';
import { retry } from './retry';

afterEach(() => vi.restoreAllMocks());

it('retries until the operation succeeds', async () => {
  const op = vi
    .fn<() => Promise<string>>()
    .mockRejectedValueOnce(new Error('network'))
    .mockResolvedValueOnce('ok');

  await expect(retry(op, { attempts: 3 })).resolves.toBe('ok');
  expect(op).toHaveBeenCalledTimes(2);
});

it('warns when it gives up', async () => {
  const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
  const op = vi.fn().mockRejectedValue(new Error('down'));

  await expect(retry(op, { attempts: 2 })).rejects.toThrow('down');
  expect(warn).toHaveBeenCalledWith(expect.stringContaining('gave up after 2'));
});

Prefer injecting dependencies

When a function receives its collaborators as arguments, a plain vi.fn() is enough and no module mocking is needed. Hard-to-mock code is often a hint to pass dependencies in.

त्वरित जाँच: What does vi.spyOn(console, "warn") do by default?

  • Wraps console.warn so calls are recorded while the original still runs
  • Deletes console.warn
  • Silences all console output globally forever
  • Turns warnings into test failures
Answer

Wraps console.warn so calls are recorded while the original still runs — Override with mockImplementation if you want it silent.