All skills
antfu avatar

/vitest

@d02c484 official
by Anthony Fuantfu/skills5.9k stars
335

Vitest fast unit testing framework powered by Vite with Jest-compatible API. Use when writing tests, mocking, configuring coverage, or working with test filtering and fixtures.

Use this Skill: https://skilld.dev/gh/antfu/skills/vitest

This session only. Nothing lands on disk.

referencesfeatures-mocking.md

≈2.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

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()

Since v4, vi.spyOn/vi.fn can mock constructors — provide a function or class implementation (an arrow function throws "not a constructor"):

const Spy = vi.spyOn(cart, 'Apples').mockImplementation(class {
  getApples() { return 0 }
})
const instance = new Spy()

Conditional Mocking with vi.when (v5)

Define per-argument behaviors without writing if/switch in mockImplementation:

vi.when(db.findById)
  .calledWith(1)
  .thenResolve({ id: 1, name: 'Ella' })
  .calledWith(2)
  .thenResolve({ id: 2, name: 'Gracie' })

// Actions: thenReturn / thenThrow / thenResolve / thenReject (+ *Once variants)
// `calledWith` supports asymmetric matchers
vi.when(sendEmail).calledWith(expect.stringContaining('@')).thenReturn({ ok: true })
  • Behaviors match first-in-first-out (register specific before broad); stacked actions on one behavior consume last-in-first-out, with { times } to limit.
  • Handle unmatched calls with { onUnmatched: 'throw' | fn } (default falls through to the original implementation).
  • Assert all behaviors ran with expect(w).toHaveBeenExhausted().

Auto-Cleanup with using

In runtimes with Explicit Resource Management (Node 24+, TS 5.2+), declare spies/mocks with using to auto-restore when the block exits — works with vi.spyOn, vi.fn, vi.doMock, and vi.when:

it('mocks console only here', () => {
  using spy = vi.spyOn(console, 'log').mockImplementation(() => {})
  debug('message')
  expect(spy).toHaveBeenCalled()
} ) // console.log restored automatically — no afterEach

Module Mocking

vi.mock, vi.unmock, and vi.hoisted are hoisted to the top of the file. In v5 calling them inside a function, block, or describe/test callback throws (it only warned before) — keep them at the module top level. Use vi.doMock/vi.doUnmock for non-hoisted, in-scope 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

v5 fake timers (and vi.setSystemTime used without them) also mock Temporal when it's on the global object, not just Date:

vi.setSystemTime(0)
Temporal.Now.instant().epochMilliseconds // 0

// keep Temporal native:
vi.useFakeTimers({ toNotFake: ['Temporal'] })

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 call history before each test — v5 DEFAULT
    mockReset: true,     // Reset before each test
    restoreMocks: true,  // Restore after each test
    unstubEnvs: true,    // Restore env vars
    unstubGlobals: true, // Restore globals
  },
})

v5: clearMocks defaults to true, so mock call history no longer leaks between tests. Mocks set up outside the test body (setup files, module top level, beforeAll) are most affected — their recorded calls are cleared before the asserting test runs. Set clearMocks: false to restore the old behavior.

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')
})

v5 Behavior Changes

  • Class mocks keep prototype methods. vi.fn(Dog), vi.spyOn(obj, 'Dog'), and .mockImplementation(class …) now chain the mock's prototype to the implementation's, so instance methods work and instanceof Dog passes. mockReset reverts the chain.
  • Automocked modules stay automocked in the browser — their exports return undefined unless you pass { spy: true } or a factory.

v4 Behavior Changes

  • vi.fn().getMockName() returns 'vi.fn()' (was 'spy'); snapshots show [MockFunction] instead of [MockFunction spy].
  • vi.restoreAllMocks (and restoreMocks: true) now only restore vi.spyOn spies; automocks are unaffected. .mockRestore still resets a mock's implementation/state.
  • vi.fn().mock.invocationCallOrder starts at 1 (Jest parity).
  • Automocked getters return undefined by default; automocked methods can't be restored.

Key Points

  • Prefer vi.mock for module mocking (hoisted - called before imports)
  • Use vi.doMock for dynamic, non-hoisted mocking
  • Use vi.when for argument-specific behaviors; using for scoped auto-restore
  • Use { spy: true } to keep implementation but track calls
  • vi.hoisted lets you reference variables in mock factories
<!-- Source references: - https://vitest.dev/guide/mocking.html - https://vitest.dev/api/vi.html - https://vitest.dev/guide/recipes/conditional-mocking - https://vitest.dev/guide/recipes/explicit-resources -->

Source: SKILL.md on GitHub

No alerts3d5 checks · Risk SAFE
  • Gen Agent Trust Hub3d

    The skill provides comprehensive documentation and reference material for the Vitest testing framework. It covers configuration, CLI usage, mocking, coverage, and advanced features like type testing and benchmarking. No security issues were detected; the instructions follow standard development and testing practices.

  • Socket3d

    No alerts

  • Snyk3d

    Risk: LOW · No issues

  • Runlayer7mo

    1/18 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at d02c484. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated 4 days ago
Other metadata
metadata
{
  "author": "Anthony Fu",
  "version": "2026.9.25",
  "source": "Generated from https://github.com/vitest-dev/vitest, scripts located at https://github.com/antfu/skills"
}

README badge

README badge for antfu/skills/vitest

Vitest is a Jest-compatible unit testing framework powered by Vite, with native ESM, TypeScript, and JSX support. Use it for writing tests with smart watch mode, mocking, fixtures, coverage reporting, and snapshot testing in projects already using Vite.

Generated from the current SKILL.md.

Is Vitest a drop-in replacement for Jest?
Mostly. Vitest provides a Jest-compatible API and can run most Jest test suites without modification, but it uses Vite's transformation pipeline instead of Jest's, so some edge cases may differ.
Does Vitest require configuration?
No. It natively supports ESM, TypeScript, and JSX out of the box and shares Vite's config, transformers, and resolvers, so many projects need minimal or no additional setup.
What coverage providers does Vitest support?
Vitest includes built-in coverage via V8 or Istanbul providers.
Can I run tests in parallel?
Yes. Vitest uses multi-threaded workers for parallel test execution and supports sharding across multiple processes.
What test environments are available?
Vitest supports node, jsdom, happy-dom, and custom environments.

Generated from the current SKILL.md. These answers refresh after source changes.