spyOn() example with getMockName() and mockImplementationOnce()
Example showing vi.spyOn() on an object method: const spy = vi.spyOn(messages, 'getLatest'). The spy has methods like getMockName() to get the spy name and mockImplementationOnce() to replace the implementation for one call. Use haHaveBeenCalledTimes() to assert call count.
vi.fn() example for passing mock as callback
Example showing vi.fn() creation and passing it as a callback: const callback = vi.fn(). The mock can then be asserted with toHaveBeenCalledWith() to verify it was called with expected arguments.
vi.spyOn() for observing method behavior
vi.spyOn() creates a spy that tracks calls to a method on an object. Use this when you need to observe the behavior of a method without replacing its implementation.
vi.fn() for custom function implementations
vi.fn() creates a mock function. Use this when you need to pass down a custom function implementation as an argument or create a new mocked entity.
vi.spyOn() and vi.fn() share the same methods
Both vi.spyOn and vi.fn share the same methods, so they have compatible interfaces for asserting and configuring mock behavior.
vi.stubGlobal mocks global variables
The vi.stubGlobal helper mocks global variables that are not present with jsdom or node by putting the value of the global variable into a globalThis object. After stubbing with vi.stubGlobal, the global can be accessed either as a direct variable or as a property of the window object.
vi.unstubAllGlobals manually unstubs all globals
The vi.unstubAllGlobals method can be called manually to restore the original values of all stubbed globals without relying on the unstubGlobals config option.
Example: stubbing IntersectionObserver as a global
```ts
import { vi } from 'vitest'
const IntersectionObserverMock = vi.fn(class {
disconnect = vi.fn()
observe = vi.fn()
takeRecords = vi.fn()
unobserve = vi.fn()
})
vi.stubGlobal('IntersectionObserver', IntersectionObserverMock)
// now you can access it as `IntersectionObserver` or `window.IntersectionObserver`
```
This example shows how to stub the IntersectionObserver global with a mock class that has disconnect, observe, takeRecords, and unobserve methods.
unstubGlobals option resets globals after each test
By default, Vitest does not reset stubbed globals. Setting the unstubGlobals option in the config to true will restore the original values of stubbed globals after each test.
vi.spyOn must be called before method execution
vi.spyOn will only spy on calls made after it is called. If a function is executed at the top level during an import or was called before vi.spyOn is invoked, vi.spyOn will not be able to report on those earlier calls. However, if the function that calls the spied method is executed after vi.spyOn, tracking works even if the module is imported as a named export in another file.
vi.mock with path string for auto-mocking
You can call vi.mock with just a path string to automatically mock a module before it is imported. If a `./__mocks__/example.js` file exists, Vitest will load it instead. Otherwise, Vitest will load the original module and replace everything recursively according to the automocking algorithm.
Automocking algorithm
When vi.mock is called with just a path and no factory, Vitest automatically replaces the module recursively with the following rules: all arrays become empty, all primitives stay untouched, all getters return undefined, all methods return undefined, all objects are deeply cloned, and all instances of classes and their prototypes are cloned.
vi.mock with spy: true example
```ts
import { expect, vi } from 'vitest'
import { answer } from './example.js'
vi.mock(import('./example.js'), { spy: true })
// calls the original implementation
expect(answer()).toBe(42)
// vitest can still track the invocations
expect(answer).toHaveBeenCalled()
```
This example shows how spy: true allows the original implementation to run while still tracking calls.
Mocking class instances with spy: true
When mocking a class with { spy: true }, mocked instances share state between the instance and its prototype. Individual instances maintain their own state, but the prototype state accumulates all calls across all instances. This allows tracking invocations on instances that are never directly exposed to the test.
Mocking class instances with spy: true example
```ts
import { expect, test, vi } from 'vitest'
import { Answer } from './answer.js'
vi.mock(import('./answer.js'), { spy: true })
test('instance inherits the state', () => {
const answer1 = new Answer(42)
const answer2 = new Answer(0)
expect(answer1.value()).toBe(42)
expect(answer1.value).toHaveBeenCalled()
expect(answer2.value).not.toHaveBeenCalled()
expect(answer2.value()).toBe(0)
expect(Answer.prototype.value).toHaveBeenCalledTimes(2)
expect(Answer.prototype.value).toHaveReturned(42)
expect(Answer.prototype.value).toHaveReturned(0)
})
```
This example shows how to track calls to class methods across different instances using the prototype state.
Mocking virtual/non-existing modules
Vitest supports mocking virtual modules that don't exist on the file system but are imported by your code. Common examples include mocking vscode APIs in unit tests. By default, Vitest will fail transforming files if it cannot find the source of the import. To support this, specify how to handle the import in your config using either test.alias to redirect imports or a Vite plugin with a resolveId hook.
Mocking virtual modules with test.alias
```ts
import { defineConfig } from 'vitest/config'
import { resolve } from 'node:path'
export default defineConfig({
test: {
alias: {
vscode: resolve(import.meta.dirname, './mock/vscode.js'),
},
},
})
```
This example shows how to redirect virtual module imports to a real file using the test.alias configuration.
Mocking virtual modules with Vite plugin
```ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
plugins: [
{
name: 'virtual-vscode',
resolveId(id) {
if (id === 'vscode') {
return 'vscode'
}
}
}
]
})
```
This example shows how to mark a virtual module as always resolved using a Vite plugin's resolveId hook. After this, you can use vi.mock as usual for that module.
Module definition in Vitest
In Vitest context, a module is a file that exports something. Using plugins, any file can be turned into a JavaScript module. The module object is a namespace object that holds dynamic references to exported identifiers—essentially an object with exported methods and properties. For example, when importing `import * as exampleObject from './example.js'`, the exampleObject is a module object that can be referenced outside the example module itself, such as in tests.
Mocking terminology: mocked vs spied modules and exports
A mocked module is a module that was completely replaced with another one. A spied module is a mocked module, but its exported methods keep the original implementation and can be tracked. A mocked export is a module export whose invocations can be tracked. A spied export is a mocked export.
vi.mock API with factory function
To mock a module completely, use the vi.mock API with a factory function as the second argument. The factory returns a new module object that completely replaces the original module. The original module will never be called. If code tries to access a method not returned from the factory, Vitest throws an error with a helpful message.
vi.mock with factory function example
```ts
import { vi } from 'vitest'
vi.mock(import('./example.js'), () => {
return {
answer() {
return 42
},
variable: 'mock',
}
})
```
This example shows how to completely replace the example.js module with a factory function that returns a new module object.
vi.fn() for trackable mocks
To make a mocked method trackable (so you can use matchers like toHaveBeenCalled), use vi.fn() instead of a plain function. A plain function cannot be tracked, but vi.fn() creates a trackable mock function.
vi.fn() with original implementation example
```ts
import { expect, vi } from 'vitest'
import { answer } from './example.js'
vi.mock(import('./example.js'), async (importOriginal) => {
const originalModule = await importOriginal()
return {
answer: vi.fn(originalModule.answer),
variable: 'mock',
}
})
expect(answer()).toBe(42)
expect(answer).toHaveBeenCalled()
expect(answer).toHaveReturned(42)
```
This example shows how to use importOriginal to get the original implementation, wrap it with vi.fn() to make it trackable while keeping the original behavior, and then verify calls and return values.
importOriginal is asynchronous
The importOriginal function passed to a vi.mock factory is asynchronous and must be awaited. It executes the original module and returns its module object.
vi.spyOn alternative to full module mocking
Instead of replacing a whole module with vi.mock, you can spy on a single exported method using vi.spyOn. To do this, import the module as a namespace object. This allows tracking without replacing the entire module. For example: `const spy = vi.spyOn(exampleObject, 'answer').mockReturnValue(0)` where exampleObject is imported as `import * as exampleObject from './example.js'`.
vi.spyOn example
```ts
import { expect, vi } from 'vitest'
import * as exampleObject from './example.js'
const spy = vi.spyOn(exampleObject, 'answer').mockReturnValue(0)
expect(exampleObject.answer()).toBe(0)
expect(exampleObject.answer).toHaveBeenCalled()
```
This example shows how to spy on a single exported method and track its calls while replacing its return value.
How Vitest transforms vi.mock calls
When Vitest detects vi.mock inside a file, it transforms every static import into a dynamic one and moves the vi.mock call to the top of the file. This allows Vitest to register the mock before the import happens without breaking ESM hoisting rules. Static imports are converted to dynamic imports, and vi.mock calls are hoisted to ensure mocks are registered before modules are imported.
Module mocking in Node/JSDOM/happy-dom environments
When running tests in emulated environments like Node, JSDOM, or happy-dom, Vitest creates a module runner that can consume Vite code. The module runner allows Vitest to hook into module evaluation and replace it with a mock if registered. This works in an ESM-like environment without using native ESM directly, allowing users to call vi.spyOn on ES Modules while bending the rules around ES Module immutability.
vi.mock setup file tip
Remember that you can call vi.mock in a setup file to apply the module mock in every test file automatically.
Dynamic import in vi.mock for TypeScript
Use dynamic import syntax `import('./example.ts')` in vi.mock calls. Vitest will strip it before code execution, but it allows TypeScript to properly validate the string and type the importOriginal method in your IDE or CLI.
Cannot mock internal method calls within same module
It is not possible to mock calls to methods that are called inside other methods of the same file. For example, if foobar() internally calls foo(), mocking foo from outside the module will not affect the foo call inside foobar, though it will affect foo calls in other modules. This is the intended behavior and there are no plans to implement a workaround.
Cannot mock internal method calls example
```ts
// foobar.js
export function foo() {
return 'foo'
}
export function foobar() {
return `${foo()}bar`
}
// foobar.test.ts
import { vi } from 'vitest'
import * as mod from './foobar.js'
// this will only affect foo outside of the original module
vi.spyOn(mod, 'foo')
vi.mock(import('./foobar.js'), async (importOriginal) => {
return {
...await importOriginal(),
// this will only affect foo outside of the original module
foo: () => 'mocked'
}
})
```
This example demonstrates that mocking foo does not affect the foo call inside foobar, only calls from outside the module.
Workaround for internal method calls: dependency injection
To test code that has internal method calls, consider refactoring your code into multiple files or use dependency injection techniques. Making the application testable is considered the responsibility of application architecture, not the test runner.
vi.mock with spy: true option
Pass { spy: true } as the second argument to vi.mock to disable automatic mocking behavior. Instead of returning undefined, all methods will call the original implementation, but you can still track these calls using matchers like toHaveBeenCalled.
Mock Service Worker for network mocking
Mock Service Worker (MSW) is recommended for mocking network requests in Vitest. It allows you to mock HTTP, WebSocket, and GraphQL network requests and is framework agnostic. MSW works by intercepting requests tests make without requiring changes to application code. In the browser, it uses the Service Worker API. In Node.js and Vitest, it uses the @mswjs/interceptors library.
MSW HTTP setup example with Vitest
To set up MSW for HTTP requests in Vitest, import afterAll, afterEach, beforeAll from vitest, setupServer from msw/node, and http, HttpResponse from msw. Define HTTP handlers using http.get() that return HttpResponse.json(). Create a server with setupServer(...handlers), start it in beforeAll() with server.listen({onUnhandledRequest: 'error'}), close it in afterAll() with server.close(), and reset handlers in afterEach() with server.resetHandlers().
MSW GraphQL setup example with Vitest
To set up MSW for GraphQL requests in Vitest, import afterAll, afterEach, beforeAll from vitest, setupServer from msw/node, and graphql, HttpResponse from msw. Define GraphQL handlers using graphql.query() that return HttpResponse.json() with the response data. Create a server with setupServer(...handlers), start it in beforeAll() with server.listen({onUnhandledRequest: 'error'}), close it in afterAll() with server.close(), and reset handlers in afterEach() with server.resetHandlers().
MSW WebSocket setup example with Vitest
To set up MSW for WebSocket requests in Vitest, import afterAll, afterEach, beforeAll from vitest, setupServer from msw/node, and ws from msw. Create a WebSocket link using ws.link() with the WebSocket URL. Define handlers that use chat.addEventListener('connection') to listen for connection events and handle messages. Create a server with setupServer(...handlers), start it in beforeAll() with server.listen({onUnhandledRequest: 'error'}), close it in afterAll() with server.close(), and reset handlers in afterEach() with server.resetHandlers().
onUnhandledRequest error option for MSW
Configuring the MSW server with onUnhandledRequest: 'error' ensures that an error is thrown whenever there is a request that does not have a corresponding request handler. This helps catch unintended network requests during testing.
MSW test isolation with resetHandlers
Call server.resetHandlers() in afterEach to reset MSW handlers after each test. This ensures test isolation by removing any test-specific handler modifications between tests.
vi.advanceTimersToNextTimer runs next scheduled timer
The vi.advanceTimersToNextTimer() function advances the fake timer to the next scheduled timer and executes it. This is useful for testing code that uses setInterval or multiple setTimeout callbacks.
vi.advanceTimersByTime advances time by specific milliseconds
The vi.advanceTimersByTime(ms) function advances the fake timer by a specific number of milliseconds. Timers scheduled for that duration or less will execute, but timers scheduled for longer will not.
Fake timers example with setTimeout
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
function executeAfterTwoHours(func) {
setTimeout(func, 1000 * 60 * 60 * 2) // 2 hours
}
function executeEveryMinute(func) {
setInterval(func, 1000 * 60) // 1 minute
}
const mock = vi.fn(() => console.log('executed'))
describe('delayed execution', () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.clearAllMocks()
})
it('should execute the function', () => {
executeAfterTwoHours(mock)
vi.runAllTimers()
expect(mock).toHaveBeenCalledTimes(1)
})
it('should not execute the function', () => {
executeAfterTwoHours(mock)
// advancing by 2ms won't trigger the func
vi.advanceTimersByTime(2)
expect(mock).not.toHaveBeenCalled()
})
it('should execute every minute', () => {
executeEveryMinute(mock)
vi.advanceTimersToNextTimer()
expect(mock).toHaveBeenCalledTimes(1)
vi.advanceTimersToNextTimer()
expect(mock).toHaveBeenCalledTimes(2)
})
})
vi.useFakeTimers enables fake timers
Call vi.useFakeTimers() to replace setTimeout and setInterval with fake implementations that can be controlled during tests, allowing tests to run instantly instead of waiting for real time to pass.
vi.runAllTimers executes all pending timers
The vi.runAllTimers() function executes all timers that are currently scheduled (both setTimeout and setInterval callbacks), advancing the fake timer to complete all pending timer operations.