Files
stack/packages/mosaic/framework/skills/vitest/references/features-mocking.md
T
fargo 1a822493ba format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
2026-08-19 14:37:17 -05:00

4.7 KiB

name, description
name description
mocking Mock functions, modules, timers, and dates with vi utilities

Mocking

Mock Functions

import { expect, vi } from 'vitest';

// Create mock function
const fn = vi.fn();
fn('hello');

expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledWith('hello');

// With implementation
const add = vi.fn((a, b) => a + b);
expect(add(1, 2)).toBe(3);

// Mock return values
fn.mockReturnValue(42);
fn.mockReturnValueOnce(1).mockReturnValueOnce(2);
fn.mockResolvedValue({ data: true });
fn.mockRejectedValue(new Error('fail'));

// Mock implementation
fn.mockImplementation((x) => x * 2);
fn.mockImplementationOnce(() => 'first call');

Spying on Objects

const cart = {
  getTotal: () => 100,
};

const spy = vi.spyOn(cart, 'getTotal');
cart.getTotal();

expect(spy).toHaveBeenCalled();

// Mock implementation
spy.mockReturnValue(200);
expect(cart.getTotal()).toBe(200);

// Restore original
spy.mockRestore();

Module Mocking

// vi.mock is hoisted to top of file
vi.mock('./api', () => ({
  fetchUser: vi.fn(() => ({ id: 1, name: 'Mock' })),
}));

import { fetchUser } from './api';

test('mocked module', () => {
  expect(fetchUser()).toEqual({ id: 1, name: 'Mock' });
});

Partial Mock

vi.mock('./utils', async (importOriginal) => {
  const actual = await importOriginal();
  return {
    ...actual,
    specificFunction: vi.fn(),
  };
});

Auto-mock with Spy

// Keep implementation but spy on calls
vi.mock('./calculator', { spy: true });

import { add } from './calculator';

test('spy on module', () => {
  const result = add(1, 2); // Real implementation
  expect(result).toBe(3);
  expect(add).toHaveBeenCalledWith(1, 2);
});

Manual Mocks (mocks)

src/
  __mocks__/
    axios.ts      # Mocks 'axios'
  api/
    __mocks__/
      client.ts   # Mocks './client'
    client.ts
// Just call vi.mock with no factory
vi.mock('axios');
vi.mock('./api/client');

Dynamic Mocking (vi.doMock)

Not hoisted - use for dynamic imports:

test('dynamic mock', async () => {
  vi.doMock('./config', () => ({
    apiUrl: 'http://test.local',
  }));

  const { apiUrl } = await import('./config');
  expect(apiUrl).toBe('http://test.local');

  vi.doUnmock('./config');
});

Mock Timers

import { afterEach, beforeEach, vi } from 'vitest';

beforeEach(() => {
  vi.useFakeTimers();
});

afterEach(() => {
  vi.useRealTimers();
});

test('timers', () => {
  const fn = vi.fn();
  setTimeout(fn, 1000);

  expect(fn).not.toHaveBeenCalled();

  vi.advanceTimersByTime(1000);
  expect(fn).toHaveBeenCalled();
});

// Other timer methods
vi.runAllTimers(); // Run all pending timers
vi.runOnlyPendingTimers(); // Run only currently pending
vi.advanceTimersToNextTimer(); // Advance to next timer

Async Timer Methods

test('async timers', async () => {
  vi.useFakeTimers();

  let resolved = false;
  setTimeout(
    () =>
      Promise.resolve().then(() => {
        resolved = true;
      }),
    100,
  );

  await vi.advanceTimersByTimeAsync(100);
  expect(resolved).toBe(true);
});

Mock Dates

vi.setSystemTime(new Date('2024-01-01'));
expect(new Date().getFullYear()).toBe(2024);

vi.useRealTimers(); // Restore

Mock Globals

vi.stubGlobal(
  'fetch',
  vi.fn(() => Promise.resolve({ json: () => ({ data: 'mock' }) })),
);

// Restore
vi.unstubAllGlobals();

Mock Environment Variables

vi.stubEnv('API_KEY', 'test-key');
expect(import.meta.env.API_KEY).toBe('test-key');

// Restore
vi.unstubAllEnvs();

Clearing Mocks

const fn = vi.fn();
fn();

fn.mockClear(); // Clear call history
fn.mockReset(); // Clear history + implementation
fn.mockRestore(); // Restore original (for spies)

// Global
vi.clearAllMocks();
vi.resetAllMocks();
vi.restoreAllMocks();

Config Auto-Reset

// vitest.config.ts
defineConfig({
  test: {
    clearMocks: true, // Clear before each test
    mockReset: true, // Reset before each test
    restoreMocks: true, // Restore after each test
    unstubEnvs: true, // Restore env vars
    unstubGlobals: true, // Restore globals
  },
});

Hoisted Variables for Mocks

const mockFn = vi.hoisted(() => vi.fn());

vi.mock('./module', () => ({
  getData: mockFn,
}));

import { getData } from './module';

test('hoisted mock', () => {
  mockFn.mockReturnValue('test');
  expect(getData()).toBe('test');
});

Key Points

  • vi.mock is hoisted - called before imports
  • Use vi.doMock for dynamic, non-hoisted mocking
  • Always restore mocks to avoid test pollution
  • Use { spy: true } to keep implementation but track calls
  • vi.hoisted lets you reference variables in mock factories