SkillByAIOpen interactive version →

Lesson 11 / 25

Module Mocking and Hoisting

jest.mock and vi.mock, and why they run first.

Replace an import for the whole test file

jest.mock('./path') / vi.mock('./path') replace a module everywhere it is imported by the file under test. With no factory, each export becomes an automatic mock; with a factory you return the replacement exports. These calls are hoisted to the top of the file, above your imports, so the mock is in place before the module loads. A consequence: a factory cannot use ordinary variables declared in the test file. In Vitest, use vi.hoisted to create values the factory may reference; in Jest, variables whose names start with mock are allowed in the factory. To keep most of the real module, spread the original (vi.importActual / jest.requireActual) and override one export. Mock your own boundary modules (an API client, analytics) rather than third-party internals.

Mocking an analytics module

Vitest with vi.hoisted; the Jest equivalent is shown below.

import { beforeEach, expect, it, vi } from 'vitest';
import { checkout } from './checkout';

const { track } = vi.hoisted(() => ({ track: vi.fn() }));

vi.mock('./analytics', () => ({ track }));          // hoisted above the imports

vi.mock('./config', async (importOriginal) => {
  const actual = await importOriginal<typeof import('./config')>();
  return { ...actual, featureFlags: { newCheckout: true } };   // override one export
});

beforeEach(() => track.mockClear());

it('tracks a completed checkout', async () => {
  await checkout({ cartId: 'c1' });
  expect(track).toHaveBeenCalledWith('checkout_completed', { cartId: 'c1' });
});

// Jest equivalent:
// const mockTrack = jest.fn();
// jest.mock('./analytics', () => ({ track: (...args: unknown[]) => mockTrack(...args) }));

A stunt double

A module mock is a stunt double: it stands in for the real actor in risky scenes. Useful, but the audience must not mistake the double's performance for the star's; you still need some tests with the real module.

Quick check: Why can a vi.mock factory not use a normal const declared above it?

  • Constants are frozen by Vitest
  • Factories may only return strings
  • Mocks run in a separate browser
  • vi.mock is hoisted above the declarations, so the variable is not initialised yet
Answer

vi.mock is hoisted above the declarations, so the variable is not initialised yet — Use vi.hoisted (or the mock prefix in Jest).