Mocking Mechanics: vi.fn, vi.spyOn, and Module Hoisting Mechanics
How Vitest hoists mocks at compilation time and intercepts ES module imports.
Part 6 in Series — Catch up on the previous article: Assertion Engine Mechanics: First-Principles Comparison of Vitest Matchers (Part 5) before diving into this post.
Mocking in ES modules is fundamentally harder than in CommonJS. In CJS, require() is a function call executed at runtime, allowing tools to override exports on the fly. In ESM, import statements are static bindings evaluated before code executes.
To mock an imported ES module, Vitest uses a compiler plugin pass during Vite’s transformation phase that hoists vi.mock() calls to the top of the file.
// What you write:
import { fetchUserData } from './api';
import { vi, test, expect } from 'vitest';
vi.mock('./api', () => ({
fetchUserData: vi.fn().mockResolvedValue({ id: 1, name: 'Adetayo' }),
}));
// What Vitest's AST transformer compiles:
import { vi } from 'vitest';
vi.mock('./api', () => ({
fetchUserData: vi.fn().mockResolvedValue({ id: 1, name: 'Adetayo' }),
}));
import { fetchUserData } from './api'; // Now receives the mocked module!
Mocking Functions: vi.fn() vs vi.spyOn()
Vitest provides tinyspy under the hood for light, memory-efficient function spying.
1. vi.fn() — Creating Standalone Mock Functions
import { vi, test, expect } from 'vitest';
const callback = vi.fn((x: number) => x * 2);
callback(5);
callback(10);
expect(callback).toHaveBeenCalledTimes(2);
expect(callback).toHaveLastReturnedWith(20);
2. vi.spyOn() — Spying on Existing Object Methods
import { vi, test, expect } from 'vitest';
const cart = {
checkout: (amount: number) => amount > 0,
};
const spy = vi.spyOn(cart, 'checkout').mockReturnValue(true);
cart.checkout(100);
expect(spy).toHaveBeenCalledWith(100);
spy.mockRestore(); // Restores original implementation
Understanding AST module hoisting ensures you never fall into temporal dead zone traps when referencing variables inside vi.mock() factories.
Part 7: Fake Timers, Microtasks, and Event Loop Time Manipulation
Continue to Part 7 →