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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user