vi.spyOn wraps an existing method
vi.spyOn() wraps an existing method on an object instead of creating a brand new function. The original implementation still works by default, but you can observe every call and optionally override the behavior with mockReturnValue or mockImplementation.
vi.fn() creates a basic mock function
vi.fn() creates a mock function that does nothing by default and returns undefined. It tracks every call made to it. A call to vi.fn() returns a tracked function that can be asserted on with expect statements like toHaveBeenCalled() and toHaveBeenCalledTimes().
Mock return values with mockReturnValue
mockReturnValue() makes a mock always return a specific value. mockReturnValueOnce() returns a specific value only once, then falls back to the default behavior. After using mockReturnValueOnce, subsequent calls return the value set by mockReturnValue if it was set, otherwise undefined.
Mock async function returns
For async functions, use mockResolvedValue() to control the resolved promise value and mockRejectedValue() to make the promise reject with an error.
mockImplementation provides custom function logic
mockImplementation() provides a full replacement function that runs when the mock is called. You can pass a function implementation directly to vi.fn() as a shorthand: vi.fn((a, b) => a + b).
Inspect mock calls with mock.calls and mock.results
The .mock property on a mock function gives access to full call history. .mock.calls is an array of argument arrays for each call. .mock.results is an array of objects with type ('return' or 'throw') and value properties showing what the mock returned or threw on each call.
Mock call inspection matchers
Use toHaveBeenCalledTimes(n) to assert the number of calls. Use toHaveBeenCalledWith(...args) to assert specific arguments were used. Use toHaveBeenNthCalledWith(n, ...args) to check arguments of a specific call by position. Use toHaveBeenLastCalledWith(...args) to check the most recent call.
mock.calls stores references not copies
mock.calls stores references to the original arguments, not copies. If you pass an object to a mock and mutate it afterwards, the recorded call will reflect the mutated state, not the state at the time of the call. To capture the original state, use mockImplementation with structuredClone to store a copy of the arguments.
Three levels of mock cleanup
mockClear() clears recorded call history and return values but keeps custom implementation. mockReset() does everything mockClear does and also removes custom implementation, returning the mock to default state. mockRestore() is for spies created with vi.spyOn and restores the original method; on vi.fn() mocks it behaves the same as mockReset.
vi.restoreAllMocks() cleans all mocks after each test
vi.restoreAllMocks() restores all mocks and spies to their original state. It is commonly used in afterEach hooks to prevent test state leakage.
restoreMocks configuration option
Set restoreMocks: true in the test configuration to automatically restore all mocks after each test, eliminating the need for manual afterEach cleanup.
vi.mock replaces entire module exports
vi.mock() replaces a module's exports with mock implementations. It takes an import statement and a factory function that returns mock implementations. vi.mock calls are hoisted to the top of the file and run before any imports, ensuring the mocked version is in place when tests run.
Use import() not string in vi.mock
Always pass import('./module.js') rather than a plain string './module.js' to vi.mock. Using import() allows TypeScript to infer module types for type-checking, enables automatic refactoring when files are moved or renamed, and allows importOriginal to return a correctly typed module.
vi.mocked() retrieves a mocked import
vi.mocked() retrieves the mocked version of an imported function or module. It is used after vi.mock to access the mock for setting up behavior like mockReturnValue.
Mock functions example
Example of creating and using a mock function:
```js
import { expect, test, vi } from 'vitest'
test('mock function basics', () => {
const getApples = vi.fn()
// Call it
getApples()
// Check it was called
expect(getApples).toHaveBeenCalled()
expect(getApples).toHaveBeenCalledTimes(1)
// By default, a mock returns undefined
expect(getApples()).toBeUndefined()
})
```
Mock return values example
Example of controlling mock return values:
```js
import { expect, test, vi } from 'vitest'
test('mock return values', () => {
const getApples = vi.fn()
// Always return this value
getApples.mockReturnValue(10)
expect(getApples()).toBe(10)
// Return this value only once, then fall back to the default
getApples.mockReturnValueOnce(20)
expect(getApples()).toBe(20) // 20 (one-time)
expect(getApples()).toBe(10) // back to default
})
```
Mock async return values example
Example of mocking async functions:
```js
test('mock async return values', async () => {
const fetchUser = vi.fn()
fetchUser.mockResolvedValue({ name: 'Alice' })
const user = await fetchUser()
expect(user.name).toBe('Alice')
fetchUser.mockRejectedValue(new Error('Not found'))
await expect(fetchUser()).rejects.toThrow('Not found')
})
```
Mock implementation example
Example of providing a custom implementation:
```js
import { expect, test, vi } from 'vitest'
test('mock with custom implementation', () => {
const add = vi.fn()
add.mockImplementation((a, b) => a + b)
expect(add(1, 2)).toBe(3)
expect(add(10, 20)).toBe(30)
})
```
Inspect mock calls example
Example of inspecting how a mock was called:
```js
import { expect, test, vi } from 'vitest'
test('inspecting mock calls', () => {
const greet = vi.fn()
greet('Alice')
greet('Bob', 'Charlie')
// Number of calls
expect(greet).toHaveBeenCalledTimes(2)
// Check specific arguments
expect(greet).toHaveBeenCalledWith('Alice')
expect(greet).toHaveBeenCalledWith('Bob', 'Charlie')
// Check the arguments of a specific call by position
expect(greet).toHaveBeenNthCalledWith(1, 'Alice')
expect(greet).toHaveBeenLastCalledWith('Bob', 'Charlie')
// Access the raw call data
expect(greet.mock.calls).toEqual([
['Alice'],
['Bob', 'Charlie'],
])
})
```
Inspect mock results example
Example of inspecting mock results:
```js
const double = vi.fn(x => x * 2)
double(5)
double(10)
expect(double.mock.results).toEqual([
{ type: 'return', value: 10 },
{ type: 'return', value: 20 },
])
```
Spy on method example
Example of spying on an existing method:
```js
import { expect, test, vi } from 'vitest'
const calculator = {
add(a, b) {
return a + b
},
}
test('spy on a method', () => {
const spy = vi.spyOn(calculator, 'add')
// The original implementation still works
expect(calculator.add(1, 2)).toBe(3)
// But we can observe calls
expect(spy).toHaveBeenCalledWith(1, 2)
expect(spy).toHaveBeenCalledTimes(1)
})
test('spy can override implementation', () => {
const spy = vi.spyOn(calculator, 'add')
spy.mockReturnValue(42)
expect(calculator.add(1, 2)).toBe(42)
})
```
Automatic mock restoration example
Example of automatically restoring mocks after each test:
```js
import { afterEach, expect, test, vi } from 'vitest'
const calculator = {
add: (a, b) => a + b,
}
afterEach(() => {
vi.restoreAllMocks()
})
test('spy is restored after the test', () => {
const spy = vi.spyOn(calculator, 'add').mockReturnValue(42)
expect(calculator.add(1, 2)).toBe(42)
// afterEach will restore calculator.add to the original implementation
})
```
Mock a module example
Example of mocking an entire module:
```js
import { expect, test, vi } from 'vitest'
import { getUser } from './db.js'
vi.mock(import('./db.js'), () => ({
getUser: vi.fn(),
}))
test('mock a module', () => {
vi.mocked(getUser).mockReturnValue({ name: 'Alice' })
const user = getUser(1)
expect(user.name).toBe('Alice')
expect(getUser).toHaveBeenCalledWith(1)
})
```
Provide dependency information for mocking
If the code under test has dependencies that need mocking, share those files or at least their type signatures. The AI cannot write a useful mock for a module it has never seen.
Avoid over-mocking in AI-generated tests
AI tends to over-mock. If you see a test that mocks every dependency and then asserts that specific internal methods were called in a specific order, that is testing implementation details rather than behavior. These tests break when you refactor, even if behavior stays the same.
AI-generated tests often don't restore mocks
AI-generated tests often set up spies with vi.spyOn or replace modules with vi.mock but never restore them. If your config doesn't have restoreMocks: true, these mocks leak between tests and cause confusing failures. Enable restoreMocks globally to fix this.
Prefer vi.mock with import() over string paths
AI tools tend to mock modules using string paths (vi.mock('./module.js')) when the import() form (vi.mock(import('./module.js'))) is preferable for type safety and automatic refactoring.
AI uses Jest APIs instead of Vitest APIs
The most frequent issue with AI-generated Vitest tests is using wrong API surface. AI models trained on Jest code sometimes generate jest.fn() instead of vi.fn(), or jest.mock instead of vi.mock. These will fail immediately. Point the AI to the Vitest API reference or include it in context.
What not to mock
Do not mock the thing you are testing. If testing a UserService, do not mock the UserService itself. Instead, mock its dependencies like the database and email sender, and let the service run for real. Prefer real implementations when they are fast and reliable, such as simple in-memory data structures or pure functions. The closer tests are to real usage, the more confidence they provide.
When to mock: slow dependencies
Mock network requests, file system operations, and database calls to keep tests fast. These operations can make tests take seconds instead of milliseconds. For HTTP requests specifically, consider using Mock Service Worker instead of mocking fetch directly.
Mocking only for slow, flaky, or side-effect dependencies
Only reach for mocks when the real thing is slow, flaky, or has side effects you cannot control in a test.
When to mock: non-deterministic values
If code depends on the current date, a random number, or a UUID generator, mock those to make tests predictable. Vitest provides vi.useFakeTimers() and vi.setSystemTime() for controlling time in tests.
vi.mock is hoisted to top of file
vi.mock calls are hoisted to the top of the file. This means the vi.mock call will always be executed before all imports, regardless of where it appears in the file.
Environmental variable values do not auto-reset
Environmental variable values set directly to import.meta.env will not automatically reset between different tests.
Mock exported class implementation with vi.mock
To mock an exported class using vi.mock, return an object with a vi.fn() that wraps a fake class: vi.mock(import('./example.js'), () => { const SomeClass = vi.fn(class FakeClass { someMethod = vi.fn() }); return { SomeClass } }).
Mock exported class implementation with vi.spyOn
To mock an exported class using vi.spyOn, spy on the class and provide a mockImplementation with a fake class: vi.spyOn(mod, 'SomeClass').mockImplementation(class FakeClass { someMethod = vi.fn() }). This approach will not work in Browser Mode.
Spy on object returned from function using cache
To spy on an object returned from a function, use vi.mock to intercept the factory and cache the mock object. Each call to the function will return the same cached object reference, allowing you to assert that methods on it were called.
Mock exported function with vi.spyOn
To mock an exported function using vi.spyOn, import the module as namespace and spy on the function: vi.spyOn(exports, 'method').mockImplementation(() => {}). This approach will not work in Browser Mode.
Mock current date with vi.setSystemTime
To mock Date and Temporal time, use vi.setSystemTime helper function. This value will not automatically reset between tests. Using vi.useFakeTimers also changes the Date time. Example: vi.setSystemTime(new Date(2022, 0, 1)); const now = new Date(); expect(now.valueOf()).toBe(new Date(2022, 0, 1).valueOf()); Reset with vi.useRealTimers().
Mock global variable with vi.stubGlobal
To mock a global variable, use vi.stubGlobal('variableName', value). This will not automatically reset between tests unless you enable the unstubGlobals config option or call vi.unstubAllGlobals(). You can also assign directly to globalThis.
Mock import.meta.env with vi.stubEnv
To mock environmental variables, assign directly to import.meta.env (which will not auto-reset), or use vi.stubEnv helper with unstubEnvs config option enabled or call vi.unstubAllEnvs manually in a beforeEach hook. Example: vi.stubEnv('VITE_ENV', 'staging'); expect(import.meta.env.VITE_ENV).toBe('staging').
Module mocking only mocks external access
When mocking part of a module, only external access to that module is mocked. If the original function calls the mocked function internally, it will always call the function defined in the module, not the mock factory.
Mocking overview and vi helper
Mocking is creating a fake version of an internal or external service. Vitest provides utility functions through the vi helper, which can be imported from 'vitest' or accessed globally if global configuration is enabled.
Clear or restore mocks between test runs
Always remember to clear or restore mocks before or after each test run to undo mock state changes between runs. See mockReset documentation for more information.
Mocking guides available in Vitest
Vitest provides comprehensive guides for mocking: Mocking Classes, Mocking Dates, Mocking the File System, Mocking Functions, Mocking Globals, Mocking Modules, Mocking Requests, and Mocking Timers.
Mock exported variables with vi.spyOn
To mock an exported variable, use vi.spyOn with the 'get' option: vi.spyOn(exports, 'getter', 'get').mockReturnValue('mocked'). This approach will not work in Browser Mode.
Mock exported function with vi.mock
To mock an exported function using vi.mock, call vi.mock with the module path and a factory function that returns an object with mocked functions. For example: vi.mock('./example.js', () => ({ method: vi.fn() })). Remember that vi.mock is hoisted to the top of the file and executes before all imports.
Mock part of a module
To mock only part of a module, use vi.mock with importOriginal to get the original module, spread it into the return object, and selectively replace exports: vi.mock(import('./some-path.js'), async (importOriginal) => { const mod = await importOriginal(); return { ...mod, mocked: vi.fn() } }). Note that this only mocks external access; if the original function calls the mocked function internally, it will call the original, not the mock.
vi.setSystemTime() to set a specific date
Use vi.setSystemTime(date) to set the system time to a specific date object. After calling this, Date.now() and new Date() will return the mocked time instead of the real time.
vi.useRealTimers() to restore real time
Call vi.useRealTimers() to restore real timers and system time after mocking. Use this in afterEach hooks to clean up after tests that use fake timers.
vi.useFakeTimers() to mock time
Call vi.useFakeTimers() to enable mocked time control in tests. This allows you to manipulate system date and timers using @sinonjs/fake-timers under the hood.
Mocking dates example with business hours
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const businessHours = [9, 17]
function purchase() {
const currentHour = new Date().getHours()
const [open, close] = businessHours
if (currentHour > open && currentHour < close) {
return { message: 'Success' }
}
return { message: 'Error' }
}
describe('purchasing flow', () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
it('allows purchases within business hours', () => {
const date = new Date(2000, 1, 1, 13)
vi.setSystemTime(date)
expect(purchase()).toEqual({ message: 'Success' })
})
it('disallows purchases outside of business hours', () => {
const date = new Date(2000, 1, 1, 19)
vi.setSystemTime(date)
expect(purchase()).toEqual({ message: 'Error' })
})
})
This example shows how to use fake timers to test time-dependent code by setting the system time to specific hours and verifying behavior during and outside business hours.
File system mocking rationale
Mocking the file system ensures tests do not depend on the actual file system, making them more reliable and predictable. This isolation helps avoid side effects from previous tests and allows testing error conditions and edge cases that might be difficult or impossible to replicate with an actual file system, such as permission issues, disk full scenarios, or read/write errors.
Vitest file system mocking approach
Vitest does not provide a file system mocking API out of the box. While you can use vi.mock to mock the fs module manually, it is hard to maintain. Instead, the recommended approach is to use memfs to create an in-memory file system that simulates file system operations without touching the actual disk. This approach is fast and safe, avoiding potential side effects on the real file system.
Setting up memfs file system mocks
To automatically redirect every fs call to memfs, create __mocks__/fs.cjs at the root of your project with content: const { fs } = require('memfs'); module.exports = fs. Also create __mocks__/fs/promises.cjs with content: const { fs } = require('memfs'); module.exports = fs.promises. You can use import syntax, but then every export must be explicitly defined.
Using vi.mock to enable file system mocks
Call vi.mock('node:fs') and vi.mock('node:fs/promises') to tell Vitest to use the fs mock files from the __mocks__ folder. This can be done in a setup file if fs should always be mocked.
Resetting memfs state in tests
Use vol.reset() in a beforeEach hook to reset the state of the in-memory file system between tests.
Example: Basic memfs file write and read
To test file operations with memfs: import { beforeEach, expect, it, vi } from 'vitest'; import { fs, vol } from 'memfs'; import { readHelloWorld } from './read-hello-world.js'; vi.mock('node:fs'); vi.mock('node:fs/promises'); beforeEach(() => { vol.reset(); }); it('should return correct text', () => { const path = '/hello-world.txt'; fs.writeFileSync(path, 'hello world'); const text = readHelloWorld(path); expect(text).toBe('hello world'); });
Example: Using vol.fromJSON to define multiple files
You can use vol.fromJSON to define several files at once: vol.fromJSON({ './dir1/hw.txt': 'hello dir1', './dir2/hw.txt': 'hello dir2', }, '/tmp'). The first parameter is an object mapping file paths to their content, and the second parameter is the default current working directory.
vi.when() for conditional mocking
vi.when() lets you define argument-specific behaviors for mocks without writing if/else logic. It allows a mock to return different values depending on the arguments it receives.