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.
This commit is contained in:
fargo
2026-08-19 14:37:17 -05:00
parent d2eeb64433
commit 1a822493ba
962 changed files with 29594 additions and 27188 deletions
@@ -3,13 +3,14 @@ name: vitest
description: 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.
metadata:
author: Anthony Fu
version: "2026.1.28"
version: '2026.1.28'
source: Generated from https://github.com/vitest-dev/vitest, scripts located at https://github.com/antfu/skills
---
Vitest is a next-generation testing framework powered by Vite. It provides a Jest-compatible API with native ESM, TypeScript, and JSX support out of the box. Vitest shares the same config, transformers, resolvers, and plugins with your Vite app.
**Key Features:**
- Vite-native: Uses Vite's transformation pipeline for fast HMR-like test updates
- Jest-compatible: Drop-in replacement for most Jest test suites
- Smart watch mode: Only reruns affected tests based on module graph
@@ -22,31 +23,31 @@ Vitest is a next-generation testing framework powered by Vite. It provides a Jes
## Core
| Topic | Description | Reference |
|-------|-------------|-----------|
| Configuration | Vitest and Vite config integration, defineConfig usage | [core-config](references/core-config.md) |
| CLI | Command line interface, commands and options | [core-cli](references/core-cli.md) |
| Test API | test/it function, modifiers like skip, only, concurrent | [core-test-api](references/core-test-api.md) |
| Describe API | describe/suite for grouping tests and nested suites | [core-describe](references/core-describe.md) |
| Expect API | Assertions with toBe, toEqual, matchers and asymmetric matchers | [core-expect](references/core-expect.md) |
| Hooks | beforeEach, afterEach, beforeAll, afterAll, aroundEach | [core-hooks](references/core-hooks.md) |
| Topic | Description | Reference |
| ------------- | --------------------------------------------------------------- | -------------------------------------------- |
| Configuration | Vitest and Vite config integration, defineConfig usage | [core-config](references/core-config.md) |
| CLI | Command line interface, commands and options | [core-cli](references/core-cli.md) |
| Test API | test/it function, modifiers like skip, only, concurrent | [core-test-api](references/core-test-api.md) |
| Describe API | describe/suite for grouping tests and nested suites | [core-describe](references/core-describe.md) |
| Expect API | Assertions with toBe, toEqual, matchers and asymmetric matchers | [core-expect](references/core-expect.md) |
| Hooks | beforeEach, afterEach, beforeAll, afterAll, aroundEach | [core-hooks](references/core-hooks.md) |
## Features
| Topic | Description | Reference |
|-------|-------------|-----------|
| Mocking | Mock functions, modules, timers, dates with vi utilities | [features-mocking](references/features-mocking.md) |
| Snapshots | Snapshot testing with toMatchSnapshot and inline snapshots | [features-snapshots](references/features-snapshots.md) |
| Coverage | Code coverage with V8 or Istanbul providers | [features-coverage](references/features-coverage.md) |
| Test Context | Test fixtures, context.expect, test.extend for custom fixtures | [features-context](references/features-context.md) |
| Concurrency | Concurrent tests, parallel execution, sharding | [features-concurrency](references/features-concurrency.md) |
| Filtering | Filter tests by name, file patterns, tags | [features-filtering](references/features-filtering.md) |
| Topic | Description | Reference |
| ------------ | -------------------------------------------------------------- | ---------------------------------------------------------- |
| Mocking | Mock functions, modules, timers, dates with vi utilities | [features-mocking](references/features-mocking.md) |
| Snapshots | Snapshot testing with toMatchSnapshot and inline snapshots | [features-snapshots](references/features-snapshots.md) |
| Coverage | Code coverage with V8 or Istanbul providers | [features-coverage](references/features-coverage.md) |
| Test Context | Test fixtures, context.expect, test.extend for custom fixtures | [features-context](references/features-context.md) |
| Concurrency | Concurrent tests, parallel execution, sharding | [features-concurrency](references/features-concurrency.md) |
| Filtering | Filter tests by name, file patterns, tags | [features-filtering](references/features-filtering.md) |
## Advanced
| Topic | Description | Reference |
|-------|-------------|-----------|
| Vi Utilities | vi helper: mock, spyOn, fake timers, hoisted, waitFor | [advanced-vi](references/advanced-vi.md) |
| Environments | Test environments: node, jsdom, happy-dom, custom | [advanced-environments](references/advanced-environments.md) |
| Type Testing | Type-level testing with expectTypeOf and assertType | [advanced-type-testing](references/advanced-type-testing.md) |
| Projects | Multi-project workspaces, different configs per project | [advanced-projects](references/advanced-projects.md) |
| Topic | Description | Reference |
| ------------ | ------------------------------------------------------- | ------------------------------------------------------------ |
| Vi Utilities | vi helper: mock, spyOn, fake timers, hoisted, waitFor | [advanced-vi](references/advanced-vi.md) |
| Environments | Test environments: node, jsdom, happy-dom, custom | [advanced-environments](references/advanced-environments.md) |
| Type Testing | Type-level testing with expectTypeOf and assertType | [advanced-type-testing](references/advanced-type-testing.md) |
| Projects | Multi-project workspaces, different configs per project | [advanced-projects](references/advanced-projects.md) |
@@ -19,7 +19,7 @@ description: Configure environments like jsdom, happy-dom for browser APIs
defineConfig({
test: {
environment: 'jsdom',
// Environment-specific options
environmentOptions: {
jsdom: {
@@ -27,7 +27,7 @@ defineConfig({
},
},
},
})
});
```
## Installing Environment Packages
@@ -47,12 +47,12 @@ Use magic comment at top of file:
```ts
// @vitest-environment jsdom
import { expect, test } from 'vitest'
import { expect, test } from 'vitest';
test('DOM test', () => {
const div = document.createElement('div')
expect(div).toBeInstanceOf(HTMLDivElement)
})
const div = document.createElement('div');
expect(div).toBeInstanceOf(HTMLDivElement);
});
```
## jsdom Environment
@@ -63,18 +63,18 @@ Full browser environment simulation:
// @vitest-environment jsdom
test('DOM manipulation', () => {
document.body.innerHTML = '<div id="app"></div>'
const app = document.getElementById('app')
app.textContent = 'Hello'
expect(app.textContent).toBe('Hello')
})
document.body.innerHTML = '<div id="app"></div>';
const app = document.getElementById('app');
app.textContent = 'Hello';
expect(app.textContent).toBe('Hello');
});
test('window APIs', () => {
expect(window.location.href).toBeDefined()
expect(localStorage).toBeDefined()
})
expect(window.location.href).toBeDefined();
expect(localStorage).toBeDefined();
});
```
### jsdom Options
@@ -91,7 +91,7 @@ defineConfig({
},
},
},
})
});
```
## happy-dom Environment
@@ -102,10 +102,10 @@ Faster but fewer APIs:
// @vitest-environment happy-dom
test('basic DOM', () => {
const el = document.createElement('div')
el.className = 'test'
expect(el.className).toBe('test')
})
const el = document.createElement('div');
el.className = 'test';
expect(el.className).toBe('test');
});
```
## Multiple Environments per Project
@@ -132,7 +132,7 @@ defineConfig({
},
],
},
})
});
```
## Custom Environment
@@ -141,23 +141,23 @@ Create custom environment package:
```ts
// vitest-environment-custom/index.ts
import type { Environment } from 'vitest/runtime'
import type { Environment } from 'vitest/runtime';
export default <Environment>{
name: 'custom',
viteEnvironment: 'ssr', // or 'client'
setup() {
// Setup global state
globalThis.myGlobal = 'value'
globalThis.myGlobal = 'value';
return {
teardown() {
delete globalThis.myGlobal
delete globalThis.myGlobal;
},
}
};
},
}
};
```
Use with:
@@ -167,7 +167,7 @@ defineConfig({
test: {
environment: 'custom',
},
})
});
```
## Environment with VM
@@ -178,23 +178,23 @@ For full isolation:
export default <Environment>{
name: 'isolated',
viteEnvironment: 'ssr',
async setupVM() {
const vm = await import('node:vm')
const context = vm.createContext()
const vm = await import('node:vm');
const context = vm.createContext();
return {
getVmContext() {
return context
return context;
},
teardown() {},
}
};
},
setup() {
return { teardown() {} }
return { teardown() {} };
},
}
};
```
## Browser Mode (Separate from Environments)
@@ -210,7 +210,7 @@ defineConfig({
provider: 'playwright',
},
},
})
});
```
## CSS and Assets
@@ -221,7 +221,7 @@ In jsdom/happy-dom, configure CSS handling:
defineConfig({
test: {
css: true, // Process CSS
// Or with options
css: {
include: /\.module\.css$/,
@@ -230,7 +230,7 @@ defineConfig({
},
},
},
})
});
```
## Fixing External Dependencies
@@ -246,7 +246,7 @@ defineConfig({
},
},
},
})
});
```
## Key Points
@@ -258,7 +258,7 @@ defineConfig({
- Use projects for multiple environment configurations
- Browser Mode is for real browser testing, not environment
<!--
<!--
Source references:
- https://vitest.dev/guide/environment.html
-->
@@ -16,7 +16,7 @@ defineConfig({
projects: [
// Glob patterns for config files
'packages/*',
// Inline config
{
test: {
@@ -34,7 +34,7 @@ defineConfig({
},
],
},
})
});
```
## Monorepo Pattern
@@ -49,14 +49,14 @@ defineConfig({
'packages/utils',
],
},
})
});
```
Package config:
```ts
// packages/core/vitest.config.ts
import { defineConfig } from 'vitest/config'
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
@@ -64,7 +64,7 @@ export default defineConfig({
include: ['src/**/*.test.ts'],
environment: 'node',
},
})
});
```
## Different Environments
@@ -93,7 +93,7 @@ defineConfig({
},
],
},
})
});
```
## Browser + Node Projects
@@ -122,7 +122,7 @@ defineConfig({
},
],
},
})
});
```
## Shared Configuration
@@ -132,10 +132,10 @@ defineConfig({
export const sharedConfig = {
testTimeout: 10000,
setupFiles: ['./tests/setup.ts'],
}
};
// vitest.config.ts
import { sharedConfig } from './vitest.shared'
import { sharedConfig } from './vitest.shared';
defineConfig({
test: {
@@ -156,7 +156,7 @@ defineConfig({
},
],
},
})
});
```
## Project-Specific Dependencies
@@ -179,7 +179,7 @@ defineConfig({
},
],
},
})
});
```
## Running Specific Projects
@@ -225,15 +225,15 @@ defineConfig({
},
],
},
})
});
// In tests, use inject
import { inject } from 'vitest'
import { inject } from 'vitest';
test('uses correct api', () => {
const url = inject('apiUrl')
expect(url).toContain('api.com')
})
const url = inject('apiUrl');
expect(url).toContain('api.com');
});
```
## With Fixtures
@@ -241,11 +241,11 @@ test('uses correct api', () => {
```ts
const test = base.extend({
apiUrl: ['/default', { injected: true }],
})
});
test('uses injected url', ({ apiUrl }) => {
// apiUrl comes from project's provide config
})
});
```
## Project Isolation
@@ -265,7 +265,7 @@ defineConfig({
},
],
},
})
});
```
## Global Setup per Project
@@ -282,7 +282,7 @@ defineConfig({
},
],
},
})
});
```
## Key Points
@@ -294,7 +294,7 @@ defineConfig({
- Use `provide` to inject config values into tests
- Projects inherit from root config unless overridden
<!--
<!--
Source references:
- https://vitest.dev/guide/projects.html
-->
@@ -13,12 +13,12 @@ Type tests use `.test-d.ts` extension:
```ts
// math.test-d.ts
import { expectTypeOf } from 'vitest'
import { add } from './math'
import { expectTypeOf } from 'vitest';
import { add } from './math';
test('add returns number', () => {
expectTypeOf(add).returns.toBeNumber()
})
expectTypeOf(add).returns.toBeNumber();
});
```
## Configuration
@@ -28,128 +28,133 @@ defineConfig({
test: {
typecheck: {
enabled: true,
// Only type check
only: false,
// Checker: 'tsc' or 'vue-tsc'
checker: 'tsc',
// Include patterns
include: ['**/*.test-d.ts'],
// tsconfig to use
tsconfig: './tsconfig.json',
},
},
})
});
```
## expectTypeOf API
```ts
import { expectTypeOf } from 'vitest'
import { expectTypeOf } from 'vitest';
// Basic type checks
expectTypeOf<string>().toBeString()
expectTypeOf<number>().toBeNumber()
expectTypeOf<boolean>().toBeBoolean()
expectTypeOf<null>().toBeNull()
expectTypeOf<undefined>().toBeUndefined()
expectTypeOf<void>().toBeVoid()
expectTypeOf<never>().toBeNever()
expectTypeOf<any>().toBeAny()
expectTypeOf<unknown>().toBeUnknown()
expectTypeOf<object>().toBeObject()
expectTypeOf<Function>().toBeFunction()
expectTypeOf<[]>().toBeArray()
expectTypeOf<symbol>().toBeSymbol()
expectTypeOf<string>().toBeString();
expectTypeOf<number>().toBeNumber();
expectTypeOf<boolean>().toBeBoolean();
expectTypeOf<null>().toBeNull();
expectTypeOf<undefined>().toBeUndefined();
expectTypeOf<void>().toBeVoid();
expectTypeOf<never>().toBeNever();
expectTypeOf<any>().toBeAny();
expectTypeOf<unknown>().toBeUnknown();
expectTypeOf<object>().toBeObject();
expectTypeOf<Function>().toBeFunction();
expectTypeOf<[]>().toBeArray();
expectTypeOf<symbol>().toBeSymbol();
```
## Value Type Checking
```ts
const value = 'hello'
expectTypeOf(value).toBeString()
const value = 'hello';
expectTypeOf(value).toBeString();
const obj = { name: 'test', count: 42 }
expectTypeOf(obj).toMatchTypeOf<{ name: string }>()
expectTypeOf(obj).toHaveProperty('name')
const obj = { name: 'test', count: 42 };
expectTypeOf(obj).toMatchTypeOf<{ name: string }>();
expectTypeOf(obj).toHaveProperty('name');
```
## Function Types
```ts
function greet(name: string): string {
return `Hello, ${name}`
return `Hello, ${name}`;
}
expectTypeOf(greet).toBeFunction()
expectTypeOf(greet).parameters.toEqualTypeOf<[string]>()
expectTypeOf(greet).returns.toBeString()
expectTypeOf(greet).toBeFunction();
expectTypeOf(greet).parameters.toEqualTypeOf<[string]>();
expectTypeOf(greet).returns.toBeString();
// Parameter checking
expectTypeOf(greet).parameter(0).toBeString()
expectTypeOf(greet).parameter(0).toBeString();
```
## Object Types
```ts
interface User {
id: number
name: string
email?: string
id: number;
name: string;
email?: string;
}
expectTypeOf<User>().toHaveProperty('id')
expectTypeOf<User>().toHaveProperty('name').toBeString()
expectTypeOf<User>().toHaveProperty('id');
expectTypeOf<User>().toHaveProperty('name').toBeString();
// Check shape
expectTypeOf({ id: 1, name: 'test' }).toMatchTypeOf<User>()
expectTypeOf({ id: 1, name: 'test' }).toMatchTypeOf<User>();
```
## Equality vs Matching
```ts
interface A { x: number }
interface B { x: number; y: string }
interface A {
x: number;
}
interface B {
x: number;
y: string;
}
// toMatchTypeOf - subset matching
expectTypeOf<B>().toMatchTypeOf<A>() // B extends A
expectTypeOf<B>().toMatchTypeOf<A>(); // B extends A
// toEqualTypeOf - exact match
expectTypeOf<A>().not.toEqualTypeOf<B>() // Not exact match
expectTypeOf<A>().toEqualTypeOf<{ x: number }>() // Exact match
expectTypeOf<A>().not.toEqualTypeOf<B>(); // Not exact match
expectTypeOf<A>().toEqualTypeOf<{ x: number }>(); // Exact match
```
## Branded Types
```ts
type UserId = number & { __brand: 'UserId' }
type PostId = number & { __brand: 'PostId' }
type UserId = number & { __brand: 'UserId' };
type PostId = number & { __brand: 'PostId' };
expectTypeOf<UserId>().not.toEqualTypeOf<PostId>()
expectTypeOf<UserId>().not.toEqualTypeOf<number>()
expectTypeOf<UserId>().not.toEqualTypeOf<PostId>();
expectTypeOf<UserId>().not.toEqualTypeOf<number>();
```
## Generic Types
```ts
function identity<T>(value: T): T {
return value
return value;
}
expectTypeOf(identity<string>).returns.toBeString()
expectTypeOf(identity<number>).returns.toBeNumber()
expectTypeOf(identity<string>).returns.toBeString();
expectTypeOf(identity<number>).returns.toBeNumber();
```
## Nullable Types
```ts
type MaybeString = string | null | undefined
type MaybeString = string | null | undefined;
expectTypeOf<MaybeString>().toBeNullable()
expectTypeOf<string>().not.toBeNullable()
expectTypeOf<MaybeString>().toBeNullable();
expectTypeOf<string>().not.toBeNullable();
```
## assertType
@@ -157,21 +162,21 @@ expectTypeOf<string>().not.toBeNullable()
Assert a value matches a type (no assertion at runtime):
```ts
import { assertType } from 'vitest'
import { assertType } from 'vitest';
function getUser(): User | null {
return { id: 1, name: 'test' }
return { id: 1, name: 'test' };
}
test('returns user', () => {
const result = getUser()
const result = getUser();
// @ts-expect-error - should fail type check
assertType<string>(result)
assertType<string>(result);
// Correct type
assertType<User | null>(result)
})
assertType<User | null>(result);
});
```
## Using @ts-expect-error
@@ -181,10 +186,10 @@ Test that code produces type error:
```ts
test('rejects wrong types', () => {
function requireString(s: string) {}
// @ts-expect-error - number not assignable to string
requireString(123)
})
requireString(123);
});
```
## Running Type Tests
@@ -206,19 +211,19 @@ Combine runtime and type tests:
```ts
// user.test.ts
import { describe, expect, expectTypeOf, test } from 'vitest'
import { createUser } from './user'
import { describe, expect, expectTypeOf, test } from 'vitest';
import { createUser } from './user';
describe('createUser', () => {
test('runtime: creates user', () => {
const user = createUser('John')
expect(user.name).toBe('John')
})
const user = createUser('John');
expect(user.name).toBe('John');
});
test('types: returns User type', () => {
expectTypeOf(createUser).returns.toMatchTypeOf<{ name: string }>()
})
})
expectTypeOf(createUser).returns.toMatchTypeOf<{ name: string }>();
});
});
```
## Key Points
@@ -230,7 +235,7 @@ describe('createUser', () => {
- Use `@ts-expect-error` to test type errors
- Run with `vitest typecheck` or `--typecheck`
<!--
<!--
Source references:
- https://vitest.dev/guide/testing-types.html
- https://vitest.dev/api/expect-typeof.html
@@ -8,48 +8,48 @@ description: vi helper for mocking, timers, utilities
The `vi` helper provides mocking and utility functions.
```ts
import { vi } from 'vitest'
import { vi } from 'vitest';
```
## Mock Functions
```ts
// Create mock
const fn = vi.fn()
const fnWithImpl = vi.fn((x) => x * 2)
const fn = vi.fn();
const fnWithImpl = vi.fn((x) => x * 2);
// Check if mock
vi.isMockFunction(fn) // true
vi.isMockFunction(fn); // true
// Mock methods
fn.mockReturnValue(42)
fn.mockReturnValueOnce(1)
fn.mockResolvedValue(data)
fn.mockRejectedValue(error)
fn.mockImplementation(() => 'result')
fn.mockImplementationOnce(() => 'once')
fn.mockReturnValue(42);
fn.mockReturnValueOnce(1);
fn.mockResolvedValue(data);
fn.mockRejectedValue(error);
fn.mockImplementation(() => 'result');
fn.mockImplementationOnce(() => 'once');
// Clear/reset
fn.mockClear() // Clear call history
fn.mockReset() // Clear history + implementation
fn.mockRestore() // Restore original (for spies)
fn.mockClear(); // Clear call history
fn.mockReset(); // Clear history + implementation
fn.mockRestore(); // Restore original (for spies)
```
## Spying
```ts
const obj = { method: () => 'original' }
const obj = { method: () => 'original' };
const spy = vi.spyOn(obj, 'method')
obj.method()
const spy = vi.spyOn(obj, 'method');
obj.method();
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalled();
// Mock implementation
spy.mockReturnValue('mocked')
spy.mockReturnValue('mocked');
// Spy on getter/setter
vi.spyOn(obj, 'prop', 'get').mockReturnValue('value')
vi.spyOn(obj, 'prop', 'get').mockReturnValue('value');
```
## Module Mocking
@@ -58,96 +58,96 @@ vi.spyOn(obj, 'prop', 'get').mockReturnValue('value')
// Hoisted to top of file
vi.mock('./module', () => ({
fn: vi.fn(),
}))
}));
// Partial mock
vi.mock('./module', async (importOriginal) => ({
...(await importOriginal()),
specificFn: vi.fn(),
}))
}));
// Spy mode - keep implementation
vi.mock('./module', { spy: true })
vi.mock('./module', { spy: true });
// Import actual module inside mock
const actual = await vi.importActual('./module')
const actual = await vi.importActual('./module');
// Import as mock
const mocked = await vi.importMock('./module')
const mocked = await vi.importMock('./module');
```
## Dynamic Mocking
```ts
// Not hoisted - use with dynamic imports
vi.doMock('./config', () => ({ key: 'value' }))
const config = await import('./config')
vi.doMock('./config', () => ({ key: 'value' }));
const config = await import('./config');
// Unmock
vi.doUnmock('./config')
vi.unmock('./module') // Hoisted
vi.doUnmock('./config');
vi.unmock('./module'); // Hoisted
```
## Reset Modules
```ts
// Clear module cache
vi.resetModules()
vi.resetModules();
// Wait for dynamic imports
await vi.dynamicImportSettled()
await vi.dynamicImportSettled();
```
## Fake Timers
```ts
vi.useFakeTimers()
vi.useFakeTimers();
setTimeout(() => console.log('done'), 1000)
setTimeout(() => console.log('done'), 1000);
// Advance time
vi.advanceTimersByTime(1000)
vi.advanceTimersByTimeAsync(1000) // For async callbacks
vi.advanceTimersToNextTimer()
vi.advanceTimersToNextFrame() // requestAnimationFrame
vi.advanceTimersByTime(1000);
vi.advanceTimersByTimeAsync(1000); // For async callbacks
vi.advanceTimersToNextTimer();
vi.advanceTimersToNextFrame(); // requestAnimationFrame
// Run all timers
vi.runAllTimers()
vi.runAllTimersAsync()
vi.runOnlyPendingTimers()
vi.runAllTimers();
vi.runAllTimersAsync();
vi.runOnlyPendingTimers();
// Clear timers
vi.clearAllTimers()
vi.clearAllTimers();
// Check state
vi.getTimerCount()
vi.isFakeTimers()
vi.getTimerCount();
vi.isFakeTimers();
// Restore
vi.useRealTimers()
vi.useRealTimers();
```
## Mock Date/Time
```ts
vi.setSystemTime(new Date('2024-01-01'))
expect(new Date().getFullYear()).toBe(2024)
vi.setSystemTime(new Date('2024-01-01'));
expect(new Date().getFullYear()).toBe(2024);
vi.getMockedSystemTime() // Get mocked date
vi.getRealSystemTime() // Get real time (ms)
vi.getMockedSystemTime(); // Get mocked date
vi.getRealSystemTime(); // Get real time (ms)
```
## Global/Env Mocking
```ts
// Stub global
vi.stubGlobal('fetch', vi.fn())
vi.unstubAllGlobals()
vi.stubGlobal('fetch', vi.fn());
vi.unstubAllGlobals();
// Stub environment
vi.stubEnv('API_KEY', 'test')
vi.stubEnv('NODE_ENV', 'test')
vi.unstubAllEnvs()
vi.stubEnv('API_KEY', 'test');
vi.stubEnv('NODE_ENV', 'test');
vi.unstubAllEnvs();
```
## Hoisted Code
@@ -155,27 +155,27 @@ vi.unstubAllEnvs()
Run code before imports:
```ts
const mock = vi.hoisted(() => vi.fn())
const mock = vi.hoisted(() => vi.fn());
vi.mock('./module', () => ({
fn: mock, // Can reference hoisted variable
}))
}));
```
## Waiting Utilities
```ts
// Wait for callback to succeed
await vi.waitFor(async () => {
const el = document.querySelector('.loaded')
expect(el).toBeTruthy()
}, { timeout: 5000, interval: 100 })
await vi.waitFor(
async () => {
const el = document.querySelector('.loaded');
expect(el).toBeTruthy();
},
{ timeout: 5000, interval: 100 },
);
// Wait for truthy value
const element = await vi.waitUntil(
() => document.querySelector('.loaded'),
{ timeout: 5000 }
)
const element = await vi.waitUntil(() => document.querySelector('.loaded'), { timeout: 5000 });
```
## Mock Object
@@ -186,16 +186,16 @@ Mock all methods of an object:
const original = {
method: () => 'real',
nested: { fn: () => 'nested' },
}
};
const mocked = vi.mockObject(original)
mocked.method() // undefined (mocked)
mocked.method.mockReturnValue('mocked')
const mocked = vi.mockObject(original);
mocked.method(); // undefined (mocked)
mocked.method.mockReturnValue('mocked');
// Spy mode
const spied = vi.mockObject(original, { spy: true })
spied.method() // 'real'
expect(spied.method).toHaveBeenCalled()
const spied = vi.mockObject(original, { spy: true });
spied.method(); // 'real'
expect(spied.method).toHaveBeenCalled();
```
## Test Configuration
@@ -204,17 +204,17 @@ expect(spied.method).toHaveBeenCalled()
vi.setConfig({
testTimeout: 10_000,
hookTimeout: 10_000,
})
});
vi.resetConfig()
vi.resetConfig();
```
## Global Mock Management
```ts
vi.clearAllMocks() // Clear all mock call history
vi.resetAllMocks() // Reset + clear implementation
vi.restoreAllMocks() // Restore originals (spies)
vi.clearAllMocks(); // Clear all mock call history
vi.resetAllMocks(); // Reset + clear implementation
vi.restoreAllMocks(); // Restore originals (spies)
```
## vi.mocked Type Helper
@@ -222,17 +222,17 @@ vi.restoreAllMocks() // Restore originals (spies)
TypeScript helper for mocked values:
```ts
import { myFn } from './module'
vi.mock('./module')
import { myFn } from './module';
vi.mock('./module');
// Type as mock
vi.mocked(myFn).mockReturnValue('typed')
vi.mocked(myFn).mockReturnValue('typed');
// Deep mocking
vi.mocked(myModule, { deep: true })
vi.mocked(myModule, { deep: true });
// Partial mock typing
vi.mocked(fn, { partial: true }).mockResolvedValue({ ok: true })
vi.mocked(fn, { partial: true }).mockResolvedValue({ ok: true });
```
## Key Points
@@ -243,7 +243,7 @@ vi.mocked(fn, { partial: true }).mockResolvedValue({ ok: true })
- Fake timers require explicit setup and teardown
- `vi.waitFor` retries until assertion passes
<!--
<!--
Source references:
- https://vitest.dev/api/vi.html
-->
@@ -146,6 +146,7 @@ vitest --merge-reports --reporter=junit
## Watch Mode Keyboard Shortcuts
In watch mode, press:
- `a` - Run all tests
- `f` - Run only failed tests
- `u` - Update snapshots
@@ -160,7 +161,7 @@ In watch mode, press:
- Both camelCase (`--testTimeout`) and kebab-case (`--test-timeout`) work
- Boolean options can be negated with `--no-` prefix
<!--
<!--
Source references:
- https://vitest.dev/guide/cli.html
-->
@@ -11,13 +11,13 @@ Vitest reads configuration from `vitest.config.ts` or `vite.config.ts`. It share
```ts
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
// test options
},
})
});
```
## Using with Existing Vite Config
@@ -27,14 +27,14 @@ Add Vitest types reference and use the `test` property:
```ts
// vite.config.ts
/// <reference types="vitest/config" />
import { defineConfig } from 'vite'
import { defineConfig } from 'vite';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
},
})
});
```
## Merging Configs
@@ -43,14 +43,17 @@ If you have separate config files, use `mergeConfig`:
```ts
// vitest.config.ts
import { defineConfig, mergeConfig } from 'vitest/config'
import viteConfig from './vite.config'
import { defineConfig, mergeConfig } from 'vitest/config';
import viteConfig from './vite.config';
export default mergeConfig(viteConfig, defineConfig({
test: {
environment: 'jsdom',
},
}))
export default mergeConfig(
viteConfig,
defineConfig({
test: {
environment: 'jsdom',
},
}),
);
```
## Common Options
@@ -60,41 +63,41 @@ defineConfig({
test: {
// Enable global APIs (describe, it, expect) without imports
globals: true,
// Test environment: 'node', 'jsdom', 'happy-dom'
environment: 'node',
// Setup files to run before each test file
setupFiles: ['./tests/setup.ts'],
// Include patterns for test files
include: ['**/*.{test,spec}.{js,ts,jsx,tsx}'],
// Exclude patterns
exclude: ['**/node_modules/**', '**/dist/**'],
// Test timeout in ms
testTimeout: 5000,
// Hook timeout in ms
hookTimeout: 10000,
// Enable watch mode by default
watch: true,
// Coverage configuration
coverage: {
provider: 'v8', // or 'istanbul'
reporter: ['text', 'html'],
include: ['src/**/*.ts'],
},
// Run tests in isolation (each file in separate process)
isolate: true,
// Pool for running tests: 'threads', 'forks', 'vmThreads'
pool: 'threads',
// Number of threads/processes
poolOptions: {
threads: {
@@ -102,20 +105,20 @@ defineConfig({
minThreads: 1,
},
},
// Automatically clear mocks between tests
clearMocks: true,
// Restore mocks between tests
restoreMocks: true,
// Retry failed tests
retry: 0,
// Stop after first failure
bail: 0,
},
})
});
```
## Conditional Configuration
@@ -128,7 +131,7 @@ export default defineConfig(({ mode }) => ({
test: {
// test options
},
}))
}));
```
## Projects (Monorepos)
@@ -156,7 +159,7 @@ defineConfig({
},
],
},
})
});
```
## Key Points
@@ -167,7 +170,7 @@ defineConfig({
- `process.env.VITEST` is set to `true` when running tests
- Test config uses `test` property, rest is Vite config
<!--
<!--
Source references:
- https://vitest.dev/guide/#configuring-vitest
- https://vitest.dev/config/
@@ -10,21 +10,21 @@ Group related tests into suites for organization and shared setup.
## Basic Usage
```ts
import { describe, expect, test } from 'vitest'
import { describe, expect, test } from 'vitest';
describe('Math', () => {
test('adds numbers', () => {
expect(1 + 1).toBe(2)
})
expect(1 + 1).toBe(2);
});
test('subtracts numbers', () => {
expect(3 - 1).toBe(2)
})
})
expect(3 - 1).toBe(2);
});
});
// Alias: suite
import { suite } from 'vitest'
suite('equivalent to describe', () => {})
import { suite } from 'vitest';
suite('equivalent to describe', () => {});
```
## Nested Suites
@@ -32,14 +32,14 @@ suite('equivalent to describe', () => {})
```ts
describe('User', () => {
describe('when logged in', () => {
test('shows dashboard', () => {})
test('can update profile', () => {})
})
test('shows dashboard', () => {});
test('can update profile', () => {});
});
describe('when logged out', () => {
test('shows login page', () => {})
})
})
test('shows login page', () => {});
});
});
```
## Suite Options
@@ -47,9 +47,9 @@ describe('User', () => {
```ts
// All tests inherit options
describe('slow tests', { timeout: 30_000 }, () => {
test('test 1', () => {}) // 30s timeout
test('test 2', () => {}) // 30s timeout
})
test('test 1', () => {}); // 30s timeout
test('test 2', () => {}); // 30s timeout
});
```
## Suite Modifiers
@@ -58,26 +58,26 @@ describe('slow tests', { timeout: 30_000 }, () => {
```ts
describe.skip('skipped suite', () => {
test('wont run', () => {})
})
test('wont run', () => {});
});
// Conditional
describe.skipIf(process.env.CI)('not in CI', () => {})
describe.runIf(!process.env.CI)('only local', () => {})
describe.skipIf(process.env.CI)('not in CI', () => {});
describe.runIf(!process.env.CI)('only local', () => {});
```
### Focus Suites
```ts
describe.only('only this suite runs', () => {
test('runs', () => {})
})
test('runs', () => {});
});
```
### Todo Suites
```ts
describe.todo('implement later')
describe.todo('implement later');
```
### Concurrent Suites
@@ -85,35 +85,35 @@ describe.todo('implement later')
```ts
// All tests run in parallel
describe.concurrent('parallel tests', () => {
test('test 1', async ({ expect }) => {})
test('test 2', async ({ expect }) => {})
})
test('test 1', async ({ expect }) => {});
test('test 2', async ({ expect }) => {});
});
```
### Sequential in Concurrent
```ts
describe.concurrent('parallel', () => {
test('concurrent 1', async () => {})
test('concurrent 1', async () => {});
describe.sequential('must be sequential', () => {
test('step 1', async () => {})
test('step 2', async () => {})
})
})
test('step 1', async () => {});
test('step 2', async () => {});
});
});
```
### Shuffle Tests
```ts
describe.shuffle('random order', () => {
test('test 1', () => {})
test('test 2', () => {})
test('test 3', () => {})
})
test('test 1', () => {});
test('test 2', () => {});
test('test 3', () => {});
});
// Or with option
describe('random', { shuffle: true }, () => {})
describe('random', { shuffle: true }, () => {});
```
## Parameterized Suites
@@ -126,9 +126,9 @@ describe.each([
{ name: 'Firefox', version: 90 },
])('$name browser', ({ name, version }) => {
test('has version', () => {
expect(version).toBeGreaterThan(0)
})
})
expect(version).toBeGreaterThan(0);
});
});
```
### describe.for
@@ -139,34 +139,34 @@ describe.for([
['Firefox', 90],
])('%s browser', ([name, version]) => {
test('has version', () => {
expect(version).toBeGreaterThan(0)
})
})
expect(version).toBeGreaterThan(0);
});
});
```
## Hooks in Suites
```ts
describe('Database', () => {
let db
let db;
beforeAll(async () => {
db = await createDb()
})
db = await createDb();
});
afterAll(async () => {
await db.close()
})
await db.close();
});
beforeEach(async () => {
await db.clear()
})
await db.clear();
});
test('insert works', async () => {
await db.insert({ name: 'test' })
expect(await db.count()).toBe(1)
})
})
await db.insert({ name: 'test' });
expect(await db.count()).toBe(1);
});
});
```
## Modifier Combinations
@@ -174,9 +174,9 @@ describe('Database', () => {
All modifiers can be chained:
```ts
describe.skip.concurrent('skipped concurrent', () => {})
describe.only.shuffle('only and shuffled', () => {})
describe.concurrent.skip('equivalent', () => {})
describe.skip.concurrent('skipped concurrent', () => {});
describe.only.shuffle('only and shuffled', () => {});
describe.concurrent.skip('equivalent', () => {});
```
## Key Points
@@ -187,7 +187,7 @@ describe.concurrent.skip('equivalent', () => {})
- Use `describe.concurrent` with context's `expect` for snapshots
- Shuffle order depends on `sequence.seed` config
<!--
<!--
Source references:
- https://vitest.dev/api/describe.html
-->
@@ -10,94 +10,94 @@ Vitest uses Chai assertions with Jest-compatible API.
## Basic Assertions
```ts
import { expect, test } from 'vitest'
import { expect, test } from 'vitest';
test('assertions', () => {
// Equality
expect(1 + 1).toBe(2) // Strict equality (===)
expect({ a: 1 }).toEqual({ a: 1 }) // Deep equality
expect(1 + 1).toBe(2); // Strict equality (===)
expect({ a: 1 }).toEqual({ a: 1 }); // Deep equality
// Truthiness
expect(true).toBeTruthy()
expect(false).toBeFalsy()
expect(null).toBeNull()
expect(undefined).toBeUndefined()
expect('value').toBeDefined()
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect('value').toBeDefined();
// Numbers
expect(10).toBeGreaterThan(5)
expect(10).toBeGreaterThanOrEqual(10)
expect(5).toBeLessThan(10)
expect(0.1 + 0.2).toBeCloseTo(0.3, 5)
expect(10).toBeGreaterThan(5);
expect(10).toBeGreaterThanOrEqual(10);
expect(5).toBeLessThan(10);
expect(0.1 + 0.2).toBeCloseTo(0.3, 5);
// Strings
expect('hello world').toMatch(/world/)
expect('hello').toContain('ell')
expect('hello world').toMatch(/world/);
expect('hello').toContain('ell');
// Arrays
expect([1, 2, 3]).toContain(2)
expect([{ a: 1 }]).toContainEqual({ a: 1 })
expect([1, 2, 3]).toHaveLength(3)
expect([1, 2, 3]).toContain(2);
expect([{ a: 1 }]).toContainEqual({ a: 1 });
expect([1, 2, 3]).toHaveLength(3);
// Objects
expect({ a: 1, b: 2 }).toHaveProperty('a')
expect({ a: 1, b: 2 }).toHaveProperty('a', 1)
expect({ a: { b: 1 } }).toHaveProperty('a.b', 1)
expect({ a: 1 }).toMatchObject({ a: 1 })
expect({ a: 1, b: 2 }).toHaveProperty('a');
expect({ a: 1, b: 2 }).toHaveProperty('a', 1);
expect({ a: { b: 1 } }).toHaveProperty('a.b', 1);
expect({ a: 1 }).toMatchObject({ a: 1 });
// Types
expect('string').toBeTypeOf('string')
expect(new Date()).toBeInstanceOf(Date)
})
expect('string').toBeTypeOf('string');
expect(new Date()).toBeInstanceOf(Date);
});
```
## Negation
```ts
expect(1).not.toBe(2)
expect({ a: 1 }).not.toEqual({ a: 2 })
expect(1).not.toBe(2);
expect({ a: 1 }).not.toEqual({ a: 2 });
```
## Error Assertions
```ts
// Sync errors - wrap in function
expect(() => throwError()).toThrow()
expect(() => throwError()).toThrow('message')
expect(() => throwError()).toThrow(/pattern/)
expect(() => throwError()).toThrow(CustomError)
expect(() => throwError()).toThrow();
expect(() => throwError()).toThrow('message');
expect(() => throwError()).toThrow(/pattern/);
expect(() => throwError()).toThrow(CustomError);
// Async errors - use rejects
await expect(asyncThrow()).rejects.toThrow('error')
await expect(asyncThrow()).rejects.toThrow('error');
```
## Promise Assertions
```ts
// Resolves
await expect(Promise.resolve(1)).resolves.toBe(1)
await expect(fetchData()).resolves.toEqual({ data: true })
await expect(Promise.resolve(1)).resolves.toBe(1);
await expect(fetchData()).resolves.toEqual({ data: true });
// Rejects
await expect(Promise.reject('error')).rejects.toBe('error')
await expect(failingFetch()).rejects.toThrow()
await expect(Promise.reject('error')).rejects.toBe('error');
await expect(failingFetch()).rejects.toThrow();
```
## Spy/Mock Assertions
```ts
const fn = vi.fn()
fn('arg1', 'arg2')
fn('arg3')
const fn = vi.fn();
fn('arg1', 'arg2');
fn('arg3');
expect(fn).toHaveBeenCalled()
expect(fn).toHaveBeenCalledTimes(2)
expect(fn).toHaveBeenCalledWith('arg1', 'arg2')
expect(fn).toHaveBeenLastCalledWith('arg3')
expect(fn).toHaveBeenNthCalledWith(1, 'arg1', 'arg2')
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledTimes(2);
expect(fn).toHaveBeenCalledWith('arg1', 'arg2');
expect(fn).toHaveBeenLastCalledWith('arg3');
expect(fn).toHaveBeenNthCalledWith(1, 'arg1', 'arg2');
expect(fn).toHaveReturned()
expect(fn).toHaveReturnedWith(value)
expect(fn).toHaveReturned();
expect(fn).toHaveReturnedWith(value);
```
## Asymmetric Matchers
@@ -108,32 +108,22 @@ Use inside `toEqual`, `toHaveBeenCalledWith`, etc:
expect({ id: 1, name: 'test' }).toEqual({
id: expect.any(Number),
name: expect.any(String),
})
});
expect({ a: 1, b: 2, c: 3 }).toEqual(
expect.objectContaining({ a: 1 })
)
expect({ a: 1, b: 2, c: 3 }).toEqual(expect.objectContaining({ a: 1 }));
expect([1, 2, 3, 4]).toEqual(
expect.arrayContaining([1, 3])
)
expect([1, 2, 3, 4]).toEqual(expect.arrayContaining([1, 3]));
expect('hello world').toEqual(
expect.stringContaining('world')
)
expect('hello world').toEqual(expect.stringContaining('world'));
expect('hello world').toEqual(
expect.stringMatching(/world$/)
)
expect('hello world').toEqual(expect.stringMatching(/world$/));
expect({ value: null }).toEqual({
value: expect.anything() // Matches anything except null/undefined
})
value: expect.anything(), // Matches anything except null/undefined
});
// Negate with expect.not
expect([1, 2]).toEqual(
expect.not.arrayContaining([3])
)
expect([1, 2]).toEqual(expect.not.arrayContaining([3]));
```
## Soft Assertions
@@ -141,8 +131,8 @@ expect([1, 2]).toEqual(
Continue test after failure:
```ts
expect.soft(1).toBe(2) // Marks test failed but continues
expect.soft(2).toBe(3) // Also runs
expect.soft(1).toBe(2); // Marks test failed but continues
expect.soft(2).toBe(3); // Also runs
// All failures reported at end
```
@@ -151,29 +141,28 @@ expect.soft(2).toBe(3) // Also runs
Retry until passes:
```ts
await expect.poll(() => fetchStatus()).toBe('ready')
await expect.poll(() => fetchStatus()).toBe('ready');
await expect.poll(
() => document.querySelector('.element'),
{ interval: 100, timeout: 5000 }
).toBeTruthy()
await expect
.poll(() => document.querySelector('.element'), { interval: 100, timeout: 5000 })
.toBeTruthy();
```
## Assertion Count
```ts
test('async assertions', async () => {
expect.assertions(2) // Exactly 2 assertions must run
expect.assertions(2); // Exactly 2 assertions must run
await doAsync((data) => {
expect(data).toBeDefined()
expect(data.id).toBe(1)
})
})
expect(data).toBeDefined();
expect(data.id).toBe(1);
});
});
test('at least one', () => {
expect.hasAssertions() // At least 1 assertion must run
})
expect.hasAssertions(); // At least 1 assertion must run
});
```
## Extending Matchers
@@ -181,18 +170,17 @@ test('at least one', () => {
```ts
expect.extend({
toBeWithinRange(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling
const pass = received >= floor && received <= ceiling;
return {
pass,
message: () =>
`expected ${received} to be within range ${floor} - ${ceiling}`,
}
message: () => `expected ${received} to be within range ${floor} - ${ceiling}`,
};
},
})
});
test('custom matcher', () => {
expect(100).toBeWithinRange(90, 110)
})
expect(100).toBeWithinRange(90, 110);
});
```
## Snapshot Assertions
@@ -213,7 +201,7 @@ expect(() => throw new Error('fail')).toThrowErrorMatchingSnapshot()
- Use context's `expect` in concurrent tests for correct tracking
- `toThrow` requires wrapping sync code in a function
<!--
<!--
Source references:
- https://vitest.dev/api/expect.html
-->
@@ -8,27 +8,27 @@ description: beforeEach, afterEach, beforeAll, afterAll, and around hooks
## Basic Hooks
```ts
import { afterAll, afterEach, beforeAll, beforeEach, test } from 'vitest'
import { afterAll, afterEach, beforeAll, beforeEach, test } from 'vitest';
beforeAll(async () => {
// Runs once before all tests in file/suite
await setupDatabase()
})
await setupDatabase();
});
afterAll(async () => {
// Runs once after all tests in file/suite
await teardownDatabase()
})
await teardownDatabase();
});
beforeEach(async () => {
// Runs before each test
await clearTestData()
})
await clearTestData();
});
afterEach(async () => {
// Runs after each test
await cleanupMocks()
})
await cleanupMocks();
});
```
## Cleanup Return Pattern
@@ -37,20 +37,20 @@ Return cleanup function from `before*` hooks:
```ts
beforeAll(async () => {
const server = await startServer()
const server = await startServer();
// Returned function runs as afterAll
return async () => {
await server.close()
}
})
await server.close();
};
});
beforeEach(async () => {
const connection = await connect()
const connection = await connect();
// Runs as afterEach
return () => connection.close()
})
return () => connection.close();
});
```
## Scoped Hooks
@@ -59,24 +59,24 @@ Hooks apply to current suite and nested suites:
```ts
describe('outer', () => {
beforeEach(() => console.log('outer before'))
test('test 1', () => {}) // outer before → test
beforeEach(() => console.log('outer before'));
test('test 1', () => {}); // outer before → test
describe('inner', () => {
beforeEach(() => console.log('inner before'))
test('test 2', () => {}) // outer before → inner before → test
})
})
beforeEach(() => console.log('inner before'));
test('test 2', () => {}); // outer before → inner before → test
});
});
```
## Hook Timeout
```ts
beforeAll(async () => {
await slowSetup()
}, 30_000) // 30 second timeout
await slowSetup();
}, 30_000); // 30 second timeout
```
## Around Hooks
@@ -84,19 +84,19 @@ beforeAll(async () => {
Wrap tests with setup/teardown context:
```ts
import { aroundEach, test } from 'vitest'
import { aroundEach, test } from 'vitest';
// Wrap each test in database transaction
aroundEach(async (runTest) => {
await db.beginTransaction()
await runTest() // Must be called!
await db.rollback()
})
await db.beginTransaction();
await runTest(); // Must be called!
await db.rollback();
});
test('insert user', async () => {
await db.insert({ name: 'Alice' })
await db.insert({ name: 'Alice' });
// Automatically rolled back after test
})
});
```
### aroundAll
@@ -104,13 +104,13 @@ test('insert user', async () => {
Wrap entire suite:
```ts
import { aroundAll, test } from 'vitest'
import { aroundAll, test } from 'vitest';
aroundAll(async (runSuite) => {
console.log('before all tests')
await runSuite() // Must be called!
console.log('after all tests')
})
console.log('before all tests');
await runSuite(); // Must be called!
console.log('after all tests');
});
```
### Multiple Around Hooks
@@ -119,16 +119,16 @@ Nested like onion layers:
```ts
aroundEach(async (runTest) => {
console.log('outer before')
await runTest()
console.log('outer after')
})
console.log('outer before');
await runTest();
console.log('outer after');
});
aroundEach(async (runTest) => {
console.log('inner before')
await runTest()
console.log('inner after')
})
console.log('inner before');
await runTest();
console.log('inner after');
});
// Order: outer before → inner before → test → inner after → outer after
```
@@ -138,41 +138,41 @@ aroundEach(async (runTest) => {
Inside test body:
```ts
import { onTestFailed, onTestFinished, test } from 'vitest'
import { onTestFailed, onTestFinished, test } from 'vitest';
test('with cleanup', () => {
const db = connect()
const db = connect();
// Runs after test finishes (pass or fail)
onTestFinished(() => db.close())
onTestFinished(() => db.close());
// Only runs if test fails
onTestFailed(({ task }) => {
console.log('Failed:', task.result?.errors)
})
db.query('SELECT * FROM users')
})
console.log('Failed:', task.result?.errors);
});
db.query('SELECT * FROM users');
});
```
### Reusable Cleanup Pattern
```ts
function useTestDb() {
const db = connect()
onTestFinished(() => db.close())
return db
const db = connect();
onTestFinished(() => db.close());
return db;
}
test('query users', () => {
const db = useTestDb()
expect(db.query('SELECT * FROM users')).toBeDefined()
})
const db = useTestDb();
expect(db.query('SELECT * FROM users')).toBeDefined();
});
test('query orders', () => {
const db = useTestDb() // Fresh connection, auto-closed
expect(db.query('SELECT * FROM orders')).toBeDefined()
})
const db = useTestDb(); // Fresh connection, auto-closed
expect(db.query('SELECT * FROM orders')).toBeDefined();
});
```
## Concurrent Test Hooks
@@ -181,9 +181,9 @@ For concurrent tests, use context's hooks:
```ts
test.concurrent('concurrent', ({ onTestFinished }) => {
const resource = allocate()
onTestFinished(() => resource.release())
})
const resource = allocate();
onTestFinished(() => resource.release());
});
```
## Extended Test Hooks
@@ -193,25 +193,26 @@ With `test.extend`, hooks are type-aware:
```ts
const test = base.extend<{ db: Database }>({
db: async ({}, use) => {
const db = await createDb()
await use(db)
await db.close()
const db = await createDb();
await use(db);
await db.close();
},
})
});
// These hooks know about `db` fixture
test.beforeEach(({ db }) => {
db.seed()
})
db.seed();
});
test.afterEach(({ db }) => {
db.clear()
})
db.clear();
});
```
## Hook Execution Order
Default order (stack):
1. `beforeAll` (in order)
2. `beforeEach` (in order)
3. Test
@@ -227,7 +228,7 @@ defineConfig({
hooks: 'list', // 'stack' (default), 'list', 'parallel'
},
},
})
});
```
## Key Points
@@ -238,7 +239,7 @@ defineConfig({
- `onTestFinished` always runs, even if test fails
- Use context hooks for concurrent tests
<!--
<!--
Source references:
- https://vitest.dev/api/hooks.html
-->
@@ -8,34 +8,34 @@ description: test/it function for defining tests with modifiers
## Basic Test
```ts
import { expect, test } from 'vitest'
import { expect, test } from 'vitest';
test('adds numbers', () => {
expect(1 + 1).toBe(2)
})
expect(1 + 1).toBe(2);
});
// Alias: it
import { it } from 'vitest'
import { it } from 'vitest';
it('works the same', () => {
expect(true).toBe(true)
})
expect(true).toBe(true);
});
```
## Async Tests
```ts
test('async test', async () => {
const result = await fetchData()
expect(result).toBeDefined()
})
const result = await fetchData();
expect(result).toBeDefined();
});
// Promises are automatically awaited
test('returns promise', () => {
return fetchData().then(result => {
expect(result).toBeDefined()
})
})
return fetchData().then((result) => {
expect(result).toBeDefined();
});
});
```
## Test Options
@@ -44,12 +44,12 @@ test('returns promise', () => {
// Timeout (default: 5000ms)
test('slow test', async () => {
// ...
}, 10_000)
}, 10_000);
// Or with options object
test('with options', { timeout: 10_000, retry: 2 }, async () => {
// ...
})
});
```
## Test Modifiers
@@ -59,17 +59,17 @@ test('with options', { timeout: 10_000, retry: 2 }, async () => {
```ts
test.skip('skipped test', () => {
// Won't run
})
});
// Conditional skip
test.skipIf(process.env.CI)('not in CI', () => {})
test.runIf(process.env.CI)('only in CI', () => {})
test.skipIf(process.env.CI)('not in CI', () => {});
test.runIf(process.env.CI)('only in CI', () => {});
// Dynamic skip via context
test('dynamic skip', ({ skip }) => {
skip(someCondition, 'reason')
skip(someCondition, 'reason');
// ...
})
});
```
### Focus Tests
@@ -77,25 +77,25 @@ test('dynamic skip', ({ skip }) => {
```ts
test.only('only this runs', () => {
// Other tests in file are skipped
})
});
```
### Todo Tests
```ts
test.todo('implement later')
test.todo('implement later');
test.todo('with body', () => {
// Not run, shows in report
})
});
```
### Failing Tests
```ts
test.fails('expected to fail', () => {
expect(1).toBe(2) // Test passes because assertion fails
})
expect(1).toBe(2); // Test passes because assertion fails
});
```
### Concurrent Tests
@@ -104,19 +104,19 @@ test.fails('expected to fail', () => {
// Run tests in parallel
test.concurrent('test 1', async ({ expect }) => {
// Use context.expect for concurrent tests
expect(await fetch1()).toBe('result')
})
expect(await fetch1()).toBe('result');
});
test.concurrent('test 2', async ({ expect }) => {
expect(await fetch2()).toBe('result')
})
expect(await fetch2()).toBe('result');
});
```
### Sequential Tests
```ts
// Force sequential in concurrent context
test.sequential('must run alone', async () => {})
test.sequential('must run alone', async () => {});
```
## Parameterized Tests
@@ -129,16 +129,16 @@ test.each([
[1, 2, 3],
[2, 1, 3],
])('add(%i, %i) = %i', (a, b, expected) => {
expect(a + b).toBe(expected)
})
expect(a + b).toBe(expected);
});
// With objects
test.each([
{ a: 1, b: 1, expected: 2 },
{ a: 1, b: 2, expected: 3 },
])('add($a, $b) = $expected', ({ a, b, expected }) => {
expect(a + b).toBe(expected)
})
expect(a + b).toBe(expected);
});
// Template literal
test.each`
@@ -146,8 +146,8 @@ test.each`
${1} | ${1} | ${2}
${1} | ${2} | ${3}
`('add($a, $b) = $expected', ({ a, b, expected }) => {
expect(a + b).toBe(expected)
})
expect(a + b).toBe(expected);
});
```
### test.for
@@ -160,8 +160,8 @@ test.for([
[1, 2, 3],
])('add(%i, %i) = %i', ([a, b, expected], { expect }) => {
// Second arg is TestContext
expect(a + b).toBe(expected)
})
expect(a + b).toBe(expected);
});
```
## Test Context
@@ -170,29 +170,29 @@ First argument provides context utilities:
```ts
test('with context', ({ expect, skip, task }) => {
console.log(task.name) // Test name
skip(someCondition) // Skip dynamically
expect(1).toBe(1) // Context-bound expect
})
console.log(task.name); // Test name
skip(someCondition); // Skip dynamically
expect(1).toBe(1); // Context-bound expect
});
```
## Custom Test with Fixtures
```ts
import { test as base } from 'vitest'
import { test as base } from 'vitest';
const test = base.extend({
db: async ({}, use) => {
const db = await createDb()
await use(db)
await db.close()
const db = await createDb();
await use(db);
await db.close();
},
})
});
test('query', async ({ db }) => {
const users = await db.query('SELECT * FROM users')
expect(users).toBeDefined()
})
const users = await db.query('SELECT * FROM users');
expect(users).toBeDefined();
});
```
## Retry Configuration
@@ -200,22 +200,26 @@ test('query', async ({ db }) => {
```ts
test('flaky test', { retry: 3 }, async () => {
// Retries up to 3 times on failure
})
});
// Advanced retry options
test('with delay', {
retry: {
count: 3,
delay: 1000,
condition: /timeout/i, // Only retry on timeout errors
test(
'with delay',
{
retry: {
count: 3,
delay: 1000,
condition: /timeout/i, // Only retry on timeout errors
},
},
}, async () => {})
async () => {},
);
```
## Tags
```ts
test('database test', { tags: ['db', 'slow'] }, async () => {})
test('database test', { tags: ['db', 'slow'] }, async () => {});
// Run with: vitest --tags db
```
@@ -227,7 +231,7 @@ test('database test', { tags: ['db', 'slow'] }, async () => {})
- Use context's `expect` for concurrent tests and snapshots
- Function name is used as test name if passed as first arg
<!--
<!--
Source references:
- https://vitest.dev/api/test.html
-->
@@ -14,15 +14,15 @@ defineConfig({
test: {
// Run files in parallel (default: true)
fileParallelism: true,
// Number of worker threads
maxWorkers: 4,
minWorkers: 1,
// Pool type: 'threads', 'forks', 'vmThreads'
pool: 'threads',
},
})
});
```
## Concurrent Tests
@@ -32,18 +32,18 @@ Run tests within a file in parallel:
```ts
// Individual concurrent tests
test.concurrent('test 1', async ({ expect }) => {
expect(await fetch1()).toBe('result')
})
expect(await fetch1()).toBe('result');
});
test.concurrent('test 2', async ({ expect }) => {
expect(await fetch2()).toBe('result')
})
expect(await fetch2()).toBe('result');
});
// All tests in suite concurrent
describe.concurrent('parallel suite', () => {
test('test 1', async ({ expect }) => {})
test('test 2', async ({ expect }) => {})
})
test('test 1', async ({ expect }) => {});
test('test 2', async ({ expect }) => {});
});
```
**Important:** Use `{ expect }` from context for concurrent tests.
@@ -54,18 +54,18 @@ Force sequential execution:
```ts
describe.concurrent('mostly parallel', () => {
test('parallel 1', async () => {})
test('parallel 2', async () => {})
test.sequential('must run alone 1', async () => {})
test.sequential('must run alone 2', async () => {})
})
test('parallel 1', async () => {});
test('parallel 2', async () => {});
test.sequential('must run alone 1', async () => {});
test.sequential('must run alone 2', async () => {});
});
// Or entire suite
describe.sequential('sequential suite', () => {
test('first', () => {})
test('second', () => {})
})
test('first', () => {});
test('second', () => {});
});
```
## Max Concurrency
@@ -77,7 +77,7 @@ defineConfig({
test: {
maxConcurrency: 5, // Max concurrent tests per file
},
})
});
```
## Isolation
@@ -90,7 +90,7 @@ defineConfig({
// Disable isolation for faster runs (less safe)
isolate: false,
},
})
});
```
## Sharding
@@ -118,7 +118,7 @@ jobs:
shard: [1, 2, 3]
steps:
- run: vitest run --shard=${{ matrix.shard }}/3 --reporter=blob
merge:
needs: test
steps:
@@ -146,18 +146,18 @@ defineConfig({
sequence: {
// Run tests in random order
shuffle: true,
// Seed for reproducible shuffle
seed: 12345,
// Hook execution order
hooks: 'stack', // 'stack', 'list', 'parallel'
// All tests concurrent by default
concurrent: true,
},
},
})
});
```
## Shuffle Tests
@@ -192,7 +192,7 @@ defineConfig({
},
},
},
})
});
```
### Forks
@@ -210,7 +210,7 @@ defineConfig({
},
},
},
})
});
```
### VM Threads
@@ -222,7 +222,7 @@ defineConfig({
test: {
pool: 'vmThreads',
},
})
});
```
## Bail on Failure
@@ -243,7 +243,7 @@ vitest --bail # Stop on first failure (same as --bail 1)
- Use `--merge-reports` to combine sharded results
- Shuffle tests to find hidden dependencies
<!--
<!--
Source references:
- https://vitest.dev/guide/features.html#running-tests-concurrently
- https://vitest.dev/guide/improving-performance.html
@@ -11,10 +11,10 @@ Every test receives context as first argument:
```ts
test('context', ({ task, expect, skip }) => {
console.log(task.name) // Test name
expect(1).toBe(1) // Context-bound expect
skip() // Skip test dynamically
})
console.log(task.name); // Test name
expect(1).toBe(1); // Context-bound expect
skip(); // Skip test dynamically
});
```
### Context Properties
@@ -30,39 +30,39 @@ test('context', ({ task, expect, skip }) => {
Create reusable test utilities:
```ts
import { test as base } from 'vitest'
import { test as base } from 'vitest';
// Define fixture types
interface Fixtures {
db: Database
user: User
db: Database;
user: User;
}
// Create extended test
export const test = base.extend<Fixtures>({
// Fixture with setup/teardown
db: async ({}, use) => {
const db = await createDatabase()
await use(db) // Provide to test
await db.close() // Cleanup
const db = await createDatabase();
await use(db); // Provide to test
await db.close(); // Cleanup
},
// Fixture depending on another fixture
user: async ({ db }, use) => {
const user = await db.createUser({ name: 'Test' })
await use(user)
await db.deleteUser(user.id)
const user = await db.createUser({ name: 'Test' });
await use(user);
await db.deleteUser(user.id);
},
})
});
```
Using fixtures:
```ts
test('query user', async ({ db, user }) => {
const found = await db.findUser(user.id)
expect(found).toEqual(user)
})
const found = await db.findUser(user.id);
expect(found).toEqual(user);
});
```
## Fixture Initialization
@@ -72,13 +72,13 @@ Fixtures only initialize when accessed:
```ts
const test = base.extend({
expensive: async ({}, use) => {
console.log('initializing') // Only runs if test uses it
await use('value')
console.log('initializing'); // Only runs if test uses it
await use('value');
},
})
});
test('no fixture', () => {}) // expensive not called
test('uses fixture', ({ expensive }) => {}) // expensive called
test('no fixture', () => {}); // expensive not called
test('uses fixture', ({ expensive }) => {}); // expensive called
```
## Auto Fixtures
@@ -89,13 +89,13 @@ Run fixture for every test:
const test = base.extend({
setup: [
async ({}, use) => {
await globalSetup()
await use()
await globalTeardown()
await globalSetup();
await use();
await globalTeardown();
},
{ auto: true } // Always run
{ auto: true }, // Always run
],
})
});
```
## Scoped Fixtures
@@ -108,13 +108,13 @@ Initialize once per file:
const test = base.extend({
connection: [
async ({}, use) => {
const conn = await connect()
await use(conn)
await conn.close()
const conn = await connect();
await use(conn);
await conn.close();
},
{ scope: 'file' }
{ scope: 'file' },
],
})
});
```
### Worker Scope
@@ -125,11 +125,11 @@ Initialize once per worker:
const test = base.extend({
sharedResource: [
async ({}, use) => {
await use(globalResource)
await use(globalResource);
},
{ scope: 'worker' }
{ scope: 'worker' },
],
})
});
```
## Injected Fixtures (from Config)
@@ -140,7 +140,7 @@ Override fixtures per project:
// test file
const test = base.extend({
apiUrl: ['/default', { injected: true }],
})
});
// vitest.config.ts
defineConfig({
@@ -154,7 +154,7 @@ defineConfig({
},
],
},
})
});
```
## Scoped Values per Suite
@@ -164,19 +164,19 @@ Override fixture for specific suite:
```ts
const test = base.extend({
environment: 'development',
})
});
describe('production tests', () => {
test.scoped({ environment: 'production' })
test.scoped({ environment: 'production' });
test('uses production', ({ environment }) => {
expect(environment).toBe('production')
})
})
expect(environment).toBe('production');
});
});
test('uses default', ({ environment }) => {
expect(environment).toBe('development')
})
expect(environment).toBe('development');
});
```
## Extended Test Hooks
@@ -186,20 +186,20 @@ Type-aware hooks with fixtures:
```ts
const test = base.extend<{ db: Database }>({
db: async ({}, use) => {
const db = await createDb()
await use(db)
await db.close()
const db = await createDb();
await use(db);
await db.close();
},
})
});
// Hooks know about fixtures
test.beforeEach(({ db }) => {
db.seed()
})
db.seed();
});
test.afterEach(({ db }) => {
db.clear()
})
db.clear();
});
```
## Composing Fixtures
@@ -209,18 +209,20 @@ Extend from another extended test:
```ts
// base-test.ts
export const test = base.extend<{ db: Database }>({
db: async ({}, use) => { /* ... */ },
})
db: async ({}, use) => {
/* ... */
},
});
// admin-test.ts
import { test as dbTest } from './base-test'
import { test as dbTest } from './base-test';
export const test = dbTest.extend<{ admin: User }>({
admin: async ({ db }, use) => {
const admin = await db.createAdmin()
await use(admin)
const admin = await db.createAdmin();
await use(admin);
},
})
});
```
## Key Points
@@ -232,7 +234,7 @@ export const test = dbTest.extend<{ admin: User }>({
- Use `{ scope: 'file' }` for expensive shared resources
- Fixtures compose - extend from extended tests
<!--
<!--
Source references:
- https://vitest.dev/guide/test-context.html
-->
@@ -21,27 +21,22 @@ defineConfig({
coverage: {
// Provider: 'v8' (default, faster) or 'istanbul' (more compatible)
provider: 'v8',
// Enable coverage
enabled: true,
// Reporters
reporter: ['text', 'json', 'html'],
// Files to include
include: ['src/**/*.{ts,tsx}'],
// Files to exclude
exclude: [
'node_modules/',
'tests/',
'**/*.d.ts',
'**/*.test.ts',
],
exclude: ['node_modules/', 'tests/', '**/*.d.ts', '**/*.test.ts'],
// Report uncovered files
all: true,
// Thresholds
thresholds: {
lines: 80,
@@ -51,7 +46,7 @@ defineConfig({
},
},
},
})
});
```
## Providers
@@ -104,10 +99,10 @@ coverage: {
functions: 75,
branches: 70,
statements: 80,
// Per-file thresholds
perFile: true,
// Auto-update thresholds (for gradual improvement)
autoUpdate: true,
},
@@ -121,7 +116,7 @@ coverage: {
```ts
/* v8 ignore next -- @preserve */
function ignored() {
return 'not covered'
return 'not covered';
}
/* v8 ignore start -- @preserve */
@@ -201,7 +196,7 @@ vitest --merge-reports --coverage --reporter=json
- Set thresholds to enforce minimum coverage
- Use `@preserve` comment to keep ignore hints
<!--
<!--
Source references:
- https://vitest.dev/guide/coverage.html
-->
@@ -62,17 +62,17 @@ Useful with lint-staged:
// .lintstagedrc.js
export default {
'*.{ts,tsx}': 'vitest related --run',
}
};
```
## Focus Tests (.only)
```ts
test.only('only this runs', () => {})
test.only('only this runs', () => {});
describe.only('only this suite', () => {
test('runs', () => {})
})
test('runs', () => {});
});
```
In CI, `.only` throws error unless configured:
@@ -82,22 +82,22 @@ defineConfig({
test: {
allowOnly: true, // Allow .only in CI
},
})
});
```
## Skip Tests
```ts
test.skip('skipped', () => {})
test.skip('skipped', () => {});
// Conditional
test.skipIf(process.env.CI)('not in CI', () => {})
test.runIf(!process.env.CI)('local only', () => {})
test.skipIf(process.env.CI)('not in CI', () => {});
test.runIf(!process.env.CI)('local only', () => {});
// Dynamic skip
test('dynamic', ({ skip }) => {
skip(someCondition, 'reason')
})
skip(someCondition, 'reason');
});
```
## Tags
@@ -105,8 +105,8 @@ test('dynamic', ({ skip }) => {
Filter by custom tags:
```ts
test('database test', { tags: ['db'] }, () => {})
test('slow test', { tags: ['slow', 'integration'] }, () => {})
test('database test', { tags: ['db'] }, () => {});
test('slow test', { tags: ['slow', 'integration'] }, () => {});
```
Run tagged tests:
@@ -125,7 +125,7 @@ defineConfig({
tags: ['db', 'slow', 'integration'],
strictTags: true, // Fail on unknown tags
},
})
});
```
## Include/Exclude Patterns
@@ -135,23 +135,20 @@ defineConfig({
test: {
// Test file patterns
include: ['**/*.{test,spec}.{ts,tsx}'],
// Exclude patterns
exclude: [
'**/node_modules/**',
'**/e2e/**',
'**/*.skip.test.ts',
],
exclude: ['**/node_modules/**', '**/e2e/**', '**/*.skip.test.ts'],
// Include source for in-source testing
includeSource: ['src/**/*.ts'],
},
})
});
```
## Watch Mode Filtering
In watch mode, press:
- `p` - Filter by filename pattern
- `t` - Filter by test name pattern
- `a` - Run all tests
@@ -169,11 +166,11 @@ vitest --project integration --project e2e
## Environment-based Filtering
```ts
const isDev = process.env.NODE_ENV === 'development'
const isCI = process.env.CI
const isDev = process.env.NODE_ENV === 'development';
const isCI = process.env.CI;
describe.skipIf(isCI)('local only tests', () => {})
describe.runIf(isDev)('dev tests', () => {})
describe.skipIf(isCI)('local only tests', () => {});
describe.runIf(isDev)('dev tests', () => {});
```
## Combining Filters
@@ -204,7 +201,7 @@ vitest list --json # JSON output
- Use `.only` for debugging, but configure CI to reject it
- Watch mode has interactive filtering
<!--
<!--
Source references:
- https://vitest.dev/guide/filtering.html
- https://vitest.dev/guide/cli.html
@@ -8,28 +8,28 @@ description: Mock functions, modules, timers, and dates with vi utilities
## Mock Functions
```ts
import { expect, vi } from 'vitest'
import { expect, vi } from 'vitest';
// Create mock function
const fn = vi.fn()
fn('hello')
const fn = vi.fn();
fn('hello');
expect(fn).toHaveBeenCalled()
expect(fn).toHaveBeenCalledWith('hello')
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledWith('hello');
// With implementation
const add = vi.fn((a, b) => a + b)
expect(add(1, 2)).toBe(3)
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'))
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')
fn.mockImplementation((x) => x * 2);
fn.mockImplementationOnce(() => 'first call');
```
## Spying on Objects
@@ -37,19 +37,19 @@ fn.mockImplementationOnce(() => 'first call')
```ts
const cart = {
getTotal: () => 100,
}
};
const spy = vi.spyOn(cart, 'getTotal')
cart.getTotal()
const spy = vi.spyOn(cart, 'getTotal');
cart.getTotal();
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalled();
// Mock implementation
spy.mockReturnValue(200)
expect(cart.getTotal()).toBe(200)
spy.mockReturnValue(200);
expect(cart.getTotal()).toBe(200);
// Restore original
spy.mockRestore()
spy.mockRestore();
```
## Module Mocking
@@ -58,43 +58,43 @@ spy.mockRestore()
// vi.mock is hoisted to top of file
vi.mock('./api', () => ({
fetchUser: vi.fn(() => ({ id: 1, name: 'Mock' })),
}))
}));
import { fetchUser } from './api'
import { fetchUser } from './api';
test('mocked module', () => {
expect(fetchUser()).toEqual({ id: 1, name: 'Mock' })
})
expect(fetchUser()).toEqual({ id: 1, name: 'Mock' });
});
```
### Partial Mock
```ts
vi.mock('./utils', async (importOriginal) => {
const actual = await importOriginal()
const actual = await importOriginal();
return {
...actual,
specificFunction: vi.fn(),
}
})
};
});
```
### Auto-mock with Spy
```ts
// Keep implementation but spy on calls
vi.mock('./calculator', { spy: true })
vi.mock('./calculator', { spy: true });
import { add } from './calculator'
import { add } from './calculator';
test('spy on module', () => {
const result = add(1, 2) // Real implementation
expect(result).toBe(3)
expect(add).toHaveBeenCalledWith(1, 2)
})
const result = add(1, 2); // Real implementation
expect(result).toBe(3);
expect(add).toHaveBeenCalledWith(1, 2);
});
```
### Manual Mocks (__mocks__)
### Manual Mocks (**mocks**)
```
src/
@@ -108,8 +108,8 @@ src/
```ts
// Just call vi.mock with no factory
vi.mock('axios')
vi.mock('./api/client')
vi.mock('axios');
vi.mock('./api/client');
```
## Dynamic Mocking (vi.doMock)
@@ -120,102 +120,109 @@ 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')
})
}));
const { apiUrl } = await import('./config');
expect(apiUrl).toBe('http://test.local');
vi.doUnmock('./config');
});
```
## Mock Timers
```ts
import { afterEach, beforeEach, vi } from 'vitest'
import { afterEach, beforeEach, vi } from 'vitest';
beforeEach(() => {
vi.useFakeTimers()
})
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers()
})
vi.useRealTimers();
});
test('timers', () => {
const fn = vi.fn()
setTimeout(fn, 1000)
expect(fn).not.toHaveBeenCalled()
vi.advanceTimersByTime(1000)
expect(fn).toHaveBeenCalled()
})
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
vi.runAllTimers(); // Run all pending timers
vi.runOnlyPendingTimers(); // Run only currently pending
vi.advanceTimersToNextTimer(); // Advance to next timer
```
### Async Timer Methods
```ts
test('async timers', async () => {
vi.useFakeTimers()
let resolved = false
setTimeout(() => Promise.resolve().then(() => { resolved = true }), 100)
await vi.advanceTimersByTimeAsync(100)
expect(resolved).toBe(true)
})
vi.useFakeTimers();
let resolved = false;
setTimeout(
() =>
Promise.resolve().then(() => {
resolved = true;
}),
100,
);
await vi.advanceTimersByTimeAsync(100);
expect(resolved).toBe(true);
});
```
## Mock Dates
```ts
vi.setSystemTime(new Date('2024-01-01'))
expect(new Date().getFullYear()).toBe(2024)
vi.setSystemTime(new Date('2024-01-01'));
expect(new Date().getFullYear()).toBe(2024);
vi.useRealTimers() // Restore
vi.useRealTimers(); // Restore
```
## Mock Globals
```ts
vi.stubGlobal('fetch', vi.fn(() =>
Promise.resolve({ json: () => ({ data: 'mock' }) })
))
vi.stubGlobal(
'fetch',
vi.fn(() => Promise.resolve({ json: () => ({ data: 'mock' }) })),
);
// Restore
vi.unstubAllGlobals()
vi.unstubAllGlobals();
```
## Mock Environment Variables
```ts
vi.stubEnv('API_KEY', 'test-key')
expect(import.meta.env.API_KEY).toBe('test-key')
vi.stubEnv('API_KEY', 'test-key');
expect(import.meta.env.API_KEY).toBe('test-key');
// Restore
vi.unstubAllEnvs()
vi.unstubAllEnvs();
```
## Clearing Mocks
```ts
const fn = vi.fn()
fn()
const fn = vi.fn();
fn();
fn.mockClear() // Clear call history
fn.mockReset() // Clear history + implementation
fn.mockRestore() // Restore original (for spies)
fn.mockClear(); // Clear call history
fn.mockReset(); // Clear history + implementation
fn.mockRestore(); // Restore original (for spies)
// Global
vi.clearAllMocks()
vi.resetAllMocks()
vi.restoreAllMocks()
vi.clearAllMocks();
vi.resetAllMocks();
vi.restoreAllMocks();
```
## Config Auto-Reset
@@ -224,30 +231,30 @@ vi.restoreAllMocks()
// 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
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
```ts
const mockFn = vi.hoisted(() => vi.fn())
const mockFn = vi.hoisted(() => vi.fn());
vi.mock('./module', () => ({
getData: mockFn,
}))
}));
import { getData } from './module'
import { getData } from './module';
test('hoisted mock', () => {
mockFn.mockReturnValue('test')
expect(getData()).toBe('test')
})
mockFn.mockReturnValue('test');
expect(getData()).toBe('test');
});
```
## Key Points
@@ -258,7 +265,7 @@ test('hoisted mock', () => {
- 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
@@ -10,12 +10,12 @@ Snapshot tests capture output and compare against stored references.
## Basic Snapshot
```ts
import { expect, test } from 'vitest'
import { expect, test } from 'vitest';
test('snapshot', () => {
const result = generateOutput()
expect(result).toMatchSnapshot()
})
const result = generateOutput();
expect(result).toMatchSnapshot();
});
```
First run creates `.snap` file:
@@ -27,7 +27,7 @@ exports['snapshot 1'] = `
"id": 1,
"name": "test"
}
`
`;
```
## Inline Snapshots
@@ -36,22 +36,22 @@ Stored directly in test file:
```ts
test('inline snapshot', () => {
const data = { foo: 'bar' }
expect(data).toMatchInlineSnapshot()
})
const data = { foo: 'bar' };
expect(data).toMatchInlineSnapshot();
});
```
Vitest updates the test file:
```ts
test('inline snapshot', () => {
const data = { foo: 'bar' }
const data = { foo: 'bar' };
expect(data).toMatchInlineSnapshot(`
{
"foo": "bar",
}
`)
})
`);
});
```
## File Snapshots
@@ -60,9 +60,9 @@ Compare against explicit file:
```ts
test('render html', async () => {
const html = renderComponent()
await expect(html).toMatchFileSnapshot('./expected/component.html')
})
const html = renderComponent();
await expect(html).toMatchFileSnapshot('./expected/component.html');
});
```
## Snapshot Hints
@@ -71,10 +71,10 @@ Add descriptive hints:
```ts
test('multiple snapshots', () => {
expect(header).toMatchSnapshot('header')
expect(body).toMatchSnapshot('body content')
expect(footer).toMatchSnapshot('footer')
})
expect(header).toMatchSnapshot('header');
expect(body).toMatchSnapshot('body content');
expect(footer).toMatchSnapshot('footer');
});
```
## Object Shape Matching
@@ -83,17 +83,17 @@ Match partial structure:
```ts
test('shape snapshot', () => {
const data = {
id: Math.random(),
const data = {
id: Math.random(),
created: new Date(),
name: 'test'
}
name: 'test',
};
expect(data).toMatchSnapshot({
id: expect.any(Number),
created: expect.any(Date),
})
})
});
});
```
## Error Snapshots
@@ -101,15 +101,15 @@ test('shape snapshot', () => {
```ts
test('error message', () => {
expect(() => {
throw new Error('Something went wrong')
}).toThrowErrorMatchingSnapshot()
})
throw new Error('Something went wrong');
}).toThrowErrorMatchingSnapshot();
});
test('inline error', () => {
expect(() => {
throw new Error('Bad input')
}).toThrowErrorMatchingInlineSnapshot(`[Error: Bad input]`)
})
throw new Error('Bad input');
}).toThrowErrorMatchingInlineSnapshot(`[Error: Bad input]`);
});
```
## Updating Snapshots
@@ -129,12 +129,12 @@ Add custom snapshot formatting:
```ts
expect.addSnapshotSerializer({
test(val) {
return val && typeof val.toJSON === 'function'
return val && typeof val.toJSON === 'function';
},
serialize(val, config, indentation, depth, refs, printer) {
return printer(val.toJSON(), config, indentation, depth, refs)
return printer(val.toJSON(), config, indentation, depth, refs);
},
})
});
```
Or via config:
@@ -145,7 +145,7 @@ defineConfig({
test: {
snapshotSerializers: ['./my-serializer.ts'],
},
})
});
```
## Snapshot Format Options
@@ -158,7 +158,7 @@ defineConfig({
escapeString: false,
},
},
})
});
```
## Concurrent Test Snapshots
@@ -167,12 +167,12 @@ Use context's expect:
```ts
test.concurrent('concurrent 1', async ({ expect }) => {
expect(await getData()).toMatchSnapshot()
})
expect(await getData()).toMatchSnapshot();
});
test.concurrent('concurrent 2', async ({ expect }) => {
expect(await getOther()).toMatchSnapshot()
})
expect(await getOther()).toMatchSnapshot();
});
```
## Snapshot File Location
@@ -185,10 +185,10 @@ Customize:
defineConfig({
test: {
resolveSnapshotPath: (testPath, snapExtension) => {
return testPath.replace('__tests__', '__snapshots__') + snapExtension
return testPath.replace('__tests__', '__snapshots__') + snapExtension;
},
},
})
});
```
## Key Points
@@ -200,7 +200,7 @@ defineConfig({
- Inline snapshots auto-update in test file
- Use context's `expect` for concurrent tests
<!--
<!--
Source references:
- https://vitest.dev/guide/snapshot.html
- https://vitest.dev/api/expect.html#tomatchsnapshot