vitest-v4

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Vitest 4 Testing Skill

Vitest 4 测试技能

Write, configure, and debug Vitest 4 test suites with Vite-native patterns.
使用Vite原生模式编写、配置和调试Vitest 4测试套件。

Before You Start

开始之前

This skill prevents 7+ common Vitest 4 mistakes and saves ~50% tokens.
MetricWithout SkillWith Skill
Setup Time~90 min~30 min
Common Errors7+0
Token UsageHigh (trial/error)Low (known patterns)
本技能可避免7+种常见Vitest 4错误,并节省约50%的token消耗。
指标未使用技能使用技能
配置时间~90分钟~30分钟
常见错误7+0
Token消耗高(反复试错)低(使用成熟模式)

Known Issues This Skill Prevents

本技能可预防的已知问题

  1. Hanging agent runs from using watch mode instead of
    vitest run
  2. Broken coverage configs from using removed
    coverage.all
    or
    coverage.extensions
  3. Browser Mode spying failures from sealed ESM namespace objects
  4. Mock leakage between tests from missing restore/reset config
  5. Invalid multi-project setup from using deprecated
    workspace
    terminology
  6. Wrong APIs from mixing Jest helpers into Vitest tests
  7. Flaky browser interactions from using synthetic helpers instead of
    vitest/browser
  8. Slow or unstable large suites from choosing the wrong execution pool or isolation mode
  1. 使用watch模式而非
    vitest run
    导致Agent运行挂起
  2. 使用已移除的
    coverage.all
    coverage.extensions
    导致覆盖率配置失效
  3. 浏览器模式下因ESM命名空间对象被密封导致Spy失败
  4. 缺少恢复/重置配置导致Mock在测试间泄漏
  5. 使用已弃用的
    workspace
    术语导致多项目配置无效
  6. 在Vitest测试中混用Jest辅助函数导致API错误
  7. 使用合成辅助函数而非
    vitest/browser
    导致浏览器交互不稳定
  8. 选择错误的执行池或隔离模式导致大型测试套件运行缓慢或不稳定

Quick Start

快速开始

Step 1: Configure Vitest 4 for agent-safe runs

步骤1:配置Vitest 4以支持Agent安全运行

typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'node',
    restoreMocks: true,
    clearMocks: true,
    coverage: {
      provider: 'v8',
      include: ['src/**/*.{ts,tsx}'],
    },
  },
});
Why this matters: Vitest 4 removed
coverage.all
and
coverage.extensions
, and agent/CI environments need one-shot execution plus automatic mock cleanup.
typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'node',
    restoreMocks: true,
    clearMocks: true,
    coverage: {
      provider: 'v8',
      include: ['src/**/*.{ts,tsx}'],
    },
  },
});
为什么这很重要: Vitest 4移除了
coverage.all
coverage.extensions
,Agent/CI环境需要一次性执行加上自动Mock清理。

Step 2: Write tests with Vitest APIs, not Jest APIs

步骤2:使用Vitest API编写测试,而非Jest API

typescript
import { describe, expect, it, vi } from 'vitest';
import { addUser } from './add-user';
import * as api from './api';

describe('addUser', () => {
  it('returns the created user', async () => {
    vi.spyOn(api, 'createUser').mockResolvedValue({ id: '1', name: 'Ada' });

    await expect(addUser('Ada')).resolves.toEqual({ id: '1', name: 'Ada' });
  });
});
Why this matters:
vi
is the supported mocking API. Mixing
jest.fn()
or Jest-only patterns causes confusing failures and poor autocomplete.
Import rule: Import
describe
,
it
,
expect
, and
vi
from
vitest
unless the project explicitly enables
globals: true
.
typescript
import { describe, expect, it, vi } from 'vitest';
import { addUser } from './add-user';
import * as api from './api';

describe('addUser', () => {
  it('返回创建的用户', async () => {
    vi.spyOn(api, 'createUser').mockResolvedValue({ id: '1', name: 'Ada' });

    await expect(addUser('Ada')).resolves.toEqual({ id: '1', name: 'Ada' });
  });
});
为什么这很重要:
vi
是官方支持的Mocking API。混用
jest.fn()
或Jest专属模式会导致难以排查的失败和糟糕的自动补全体验。
导入规则: 除非项目明确启用
globals: true
,否则请从
vitest
导入
describe
it
expect
vi

Step 3: Use the correct runtime command

步骤3:使用正确的运行命令

bash
vitest run
vitest run --coverage
vitest run path/to/example.test.ts
Why this matters:
vitest
without
run
starts watch mode by default in development, which is a poor fit for agents, CI, and non-interactive verification.
bash
vitest run
vitest run --coverage
vitest run path/to/example.test.ts
为什么这很重要: 在开发环境中,不带
run
vitest
默认启动watch模式,这并不适合Agent、CI和非交互式验证场景。

Critical Rules

关键规则

Always Do

必须遵守

  • Use
    vitest run
    or
    vitest --no-watch
    for agent and CI workflows
  • Prefer
    vi.mock(import('./module'))
    for type-safe module mocks
  • Configure
    restoreMocks
    ,
    clearMocks
    , or
    mockReset
    intentionally
  • Use
    projects
    for multi-project configs; the rename began in Vitest 3.2 and older workspace-file usage is removed in Vitest 4
  • Use a shared base config when multiple
    projects
    need common settings; projects do not inherit root config unless you opt in
  • Use
    coverage.include
    to report on untested source files
  • Use
    page
    and
    userEvent
    from
    vitest/browser
    in Browser Mode
  • Prefer
    forks
    when native modules or runtime compatibility matter more than raw speed
  • Share
    vitest.config.ts
    and the implementation file when asking AI to generate tests
  • 对于Agent和CI工作流,使用
    vitest run
    vitest --no-watch
  • 优先使用
    vi.mock(import('./module'))
    实现类型安全的模块Mock
  • 有意配置
    restoreMocks
    clearMocks
    mockReset
  • 多项目配置使用
    projects
    ;该重命名始于Vitest 3.2,Vitest 4已移除旧版workspace文件用法
  • 当多个
    projects
    需要通用设置时,使用共享基础配置;项目不会自动继承根配置,除非你主动开启
  • 使用
    coverage.include
    报告未测试的源文件
  • 在浏览器模式中使用
    vitest/browser
    提供的
    page
    userEvent
  • 当原生模块或运行时兼容性比原始速度更重要时,优先使用
    forks
  • 请求AI生成测试时,共享
    vitest.config.ts
    和实现文件

Never Do

绝对禁止

  • Never use
    jest.fn
    ,
    jest.spyOn
    , or Jest-only globals in Vitest code
  • Never rely on removed
    coverage.all
    or
    coverage.extensions
    in Vitest 4
  • Never use plain watch mode for agent-driven verification
  • Never use
    vi.spyOn
    on native ESM exports in Browser Mode
  • Never forget that
    vi.mock()
    is hoisted before the rest of the file executes
  • Never leave env/global stubs un-restored across tests
  • 绝不在Vitest代码中使用
    jest.fn
    jest.spyOn
    或Jest专属全局变量
  • 绝不在Vitest 4中依赖已移除的
    coverage.all
    coverage.extensions
  • 绝不为Agent驱动的验证使用普通watch模式
  • 绝不在浏览器模式下对原生ESM导出使用
    vi.spyOn
  • 绝不要忘记
    vi.mock()
    会在文件其余代码执行前被提升
  • 绝不要让环境/全局存根在测试间未恢复

Common Mistakes

常见错误

Wrong - removed coverage option:
typescript
export default defineConfig({
  test: {
    coverage: {
      all: true,
    },
  },
});
Correct - use include globs:
typescript
export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',
      include: ['src/**/*.{ts,tsx}'],
    },
  },
});
Why: Vitest 4 removed
coverage.all
and
coverage.extensions
;
coverage.include
is the supported way to include uncovered files.
Wrong - Browser Mode spy on sealed export:
typescript
import * as math from './math';
import { vi } from 'vitest';

vi.spyOn(math, 'add').mockReturnValue(10);
Correct - use spy-enabled module mock:
typescript
import { vi } from 'vitest';

vi.mock(import('./math'), { spy: true });
Why: Native browser ESM namespace objects are sealed, so direct spies on exports fail in Browser Mode.
错误用法 - 已移除的覆盖率选项:
typescript
export default defineConfig({
  test: {
    coverage: {
      all: true,
    },
  },
});
正确用法 - 使用include通配符:
typescript
export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',
      include: ['src/**/*.{ts,tsx}'],
    },
  },
});
原因: Vitest 4移除了
coverage.all
coverage.extensions
coverage.include
是包含未覆盖文件的官方支持方式。
错误用法 - 浏览器模式下对密封导出使用Spy:
typescript
import * as math from './math';
import { vi } from 'vitest';

vi.spyOn(math, 'add').mockReturnValue(10);
正确用法 - 使用支持Spy的模块Mock:
typescript
import { vi } from 'vitest';

vi.mock(import('./math'), { spy: true });
原因: 原生浏览器ESM命名空间对象是密封的,因此在浏览器模式下直接对导出进行Spy会失败。

Known Issues Prevention

已知问题预防

IssueRoot CauseSolution
Tests never exitWatch mode started in a non-interactive sessionUse
vitest run
Coverage report misses untested files
coverage.include
not configured
Add explicit source globs
Browser Mode spy throws or does nothing
vi.spyOn
used on sealed ESM exports
Use
vi.mock(import('./mod'), { spy: true })
Mocks leak between testsCleanup flags missingEnable
restoreMocks
/
clearMocks
/
unstubEnvs
Multi-project config breaks after upgradeDeprecated workspace terminology or removed workspace-file patterns carried overSwitch to
projects
and
defineProject
Worker or pool config stops workingOld
maxThreads
,
maxForks
, or
poolOptions
carried forward
Migrate to Vitest 4 worker settings such as
maxWorkers
Project-specific config unexpectedly disappearsRoot config assumptions are not inherited into
projects
Use
extends: true
,
mergeConfig
, or a shared base explicitly
AI-generated tests use wrong helpersJest patterns copied into VitestReplace with
vi
, Vitest imports, and Vitest matchers
Browser tests hangBlocking dialogs or wrong user-event utilitiesMock dialogs and use
vitest/browser
helpers
Fast pool causes strange native-module failures
threads
chosen for a suite that needs process isolation
Switch to
forks
or narrow thread usage
问题根本原因解决方案
测试永不退出在非交互式会话中启动了watch模式使用
vitest run
覆盖率报告遗漏未测试文件未配置
coverage.include
添加明确的源文件通配符
浏览器模式Spy抛出错误或无效果对密封ESM导出使用了
vi.spyOn
使用
vi.mock(import('./mod'), { spy: true })
Mock在测试间泄漏缺少清理标志启用
restoreMocks
/
clearMocks
/
unstubEnvs
升级后多项目配置失效沿用了已弃用的workspace术语或已移除的workspace文件模式切换为
projects
defineProject
Worker或池配置停止工作沿用了旧版的
maxThreads
maxForks
poolOptions
迁移到Vitest 4的Worker设置,如
maxWorkers
项目特定配置意外消失错误假设根配置会被
projects
继承
明确使用
extends: true
mergeConfig
或共享基础配置
AI生成的测试使用错误的辅助函数将Jest模式复制到Vitest中替换为
vi
、Vitest导入和Vitest匹配器
浏览器测试挂起阻塞对话框或使用了错误的用户事件工具Mock对话框并使用
vitest/browser
辅助函数
Fast pool导致奇怪的原生模块失败为需要进程隔离的套件选择了
threads
切换为
forks
或缩小线程使用范围

Bundled Resources

内置资源

References

参考文档

  • Mocking rules and hoisting
    references/mocking-reference.md
  • Browser Mode providers and pitfalls
    references/browser-mode-reference.md
  • Coverage and multi-project config
    references/coverage-projects-reference.md
  • Pools, isolation, and persistent cache
    references/pools-execution-reference.md
  • Reference index
    references/README.md
  • Mocking规则与提升机制
    references/mocking-reference.md
  • 浏览器模式提供商与陷阱
    references/browser-mode-reference.md
  • 覆盖率与多项目配置
    references/coverage-projects-reference.md
  • 执行池、隔离与持久缓存
    references/pools-execution-reference.md
  • 参考索引
    references/README.md

Configuration Reference

配置参考

vitest.config.ts

vitest.config.ts

typescript
import { defineConfig, defineProject } from 'vitest/config';
import { playwright } from '@vitest/browser-playwright';

export default defineConfig({
  test: {
    projects: [
      defineProject({
        test: {
          name: 'unit',
          include: ['src/**/*.test.ts'],
          environment: 'node',
        },
      }),
      defineProject({
        test: {
          name: 'browser',
          include: ['src/**/*.browser.test.ts'],
          browser: {
            enabled: true,
            provider: playwright(),
            instances: [{ browser: 'chromium' }],
          },
        },
      }),
    ],
    coverage: {
      provider: 'v8',
      include: ['src/**/*.{ts,tsx}'],
    },
    restoreMocks: true,
    unstubEnvs: true,
    setupFiles: ['./test/setup.ts'],
  },
});
Key settings:
  • test.projects
    : Stable multi-project terminology; the rename started in Vitest 3.2, and projects do not automatically inherit every root config value, so shared settings should be factored into a reused base when needed
  • coverage.include
    : Required when uncovered source files must appear in the report
  • browser.provider
    : In Vitest 4, import the provider factory from the provider package, such as
    playwright()
  • restoreMocks
    /
    unstubEnvs
    : Prevent test pollution across files
  • setupFiles
    : Run shared test initialization such as MSW, globals, or polyfills before test files
typescript
import { defineConfig, defineProject } from 'vitest/config';
import { playwright } from '@vitest/browser-playwright';

export default defineConfig({
  test: {
    projects: [
      defineProject({
        test: {
          name: 'unit',
          include: ['src/**/*.test.ts'],
          environment: 'node',
        },
      }),
      defineProject({
        test: {
          name: 'browser',
          include: ['src/**/*.browser.test.ts'],
          browser: {
            enabled: true,
            provider: playwright(),
            instances: [{ browser: 'chromium' }],
          },
        },
      }),
    ],
    coverage: {
      provider: 'v8',
      include: ['src/**/*.{ts,tsx}'],
    },
    restoreMocks: true,
    unstubEnvs: true,
    setupFiles: ['./test/setup.ts'],
  },
});
关键设置:
  • test.projects
    : 稳定的多项目术语;重命名始于Vitest 3.2,项目不会自动继承所有根配置值,因此需要时应将共享设置提取到可复用的基础配置中
  • coverage.include
    : 当未覆盖的源文件必须出现在报告中时,此配置是必需的
  • browser.provider
    : 在Vitest 4中,从提供商包导入提供商工厂,例如
    playwright()
  • restoreMocks
    /
    unstubEnvs
    : 防止测试文件间的污染
  • setupFiles
    : 在测试文件运行前执行共享测试初始化,如MSW、全局变量或polyfills

Project Structure

项目结构

my-app/
├── src/
│   ├── feature.ts
│   ├── feature.test.ts
│   └── feature.browser.test.ts
├── vitest.config.ts
├── vite.config.ts
└── package.json
Why this matters: Keeping Node and Browser Mode tests clearly separated makes provider setup, test selection, and troubleshooting much simpler.
Choose the right environment: Prefer
jsdom
for most component tests and lightweight DOM assertions. Use Browser Mode when native browser APIs, real layout/event behavior, or screenshot assertions matter.
Choose the right execution model: Prefer
forks
for stability and native-module compatibility, especially in mixed or infrastructure-heavy suites. Reach for
threads
only when you know the test environment is safe for worker-thread execution and the extra speed matters.
my-app/
├── src/
│   ├── feature.ts
│   ├── feature.test.ts
│   └── feature.browser.test.ts
├── vitest.config.ts
├── vite.config.ts
└── package.json
为什么这很重要: 清晰分离Node和浏览器模式测试可大幅简化提供商配置、测试选择和故障排查。
选择合适的环境: 大多数组件测试和轻量级DOM断言优先使用
jsdom
。当需要原生浏览器API、真实布局/事件行为或截图断言时,使用浏览器模式。
选择合适的执行模型: 为了稳定性和原生模块兼容性,优先使用
forks
,尤其是在混合或基础设施密集型套件中。仅当确定测试环境适合线程执行且额外速度至关重要时,才使用
threads

Common Patterns

常见模式

Type-safe module mock pattern

类型安全模块Mock模式

typescript
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getUserName } from './get-user-name';
import * as api from './api';

vi.mock(import('./api'), () => ({
  fetchUser: vi.fn(),
}));

describe('getUserName', () => {
  beforeEach(() => {
    vi.mocked(api.fetchUser).mockReset();
  });

  it('returns the fetched user name', async () => {
    vi.mocked(api.fetchUser).mockResolvedValue({ id: '1', name: 'Ada' });

    await expect(getUserName('1')).resolves.toBe('Ada');
  });
});
typescript
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getUserName } from './get-user-name';
import * as api from './api';

vi.mock(import('./api'), () => ({
  fetchUser: vi.fn(),
}));

describe('getUserName', () => {
  beforeEach(() => {
    vi.mocked(api.fetchUser).mockReset();
  });

  it('返回获取到的用户名', async () => {
    vi.mocked(api.fetchUser).mockResolvedValue({ id: '1', name: 'Ada' });

    await expect(getUserName('1')).resolves.toBe('Ada');
  });
});

Browser Mode interaction pattern

浏览器模式交互模式

typescript
import { expect, test } from 'vitest';
import { page, userEvent } from 'vitest/browser';
import { render } from 'vitest-browser-react';
import { Counter } from './counter';

test('increments after click', async () => {
  render(<Counter />);

  await userEvent.click(page.getByRole('button', { name: /increment/i }));

  await expect.element(page.getByText('1')).toBeInTheDocument();
});
typescript
import { expect, test } from 'vitest';
import { page, userEvent } from 'vitest/browser';
import { render } from 'vitest-browser-react';
import { Counter } from './counter';

test('点击后递增', async () => {
  render(<Counter />);

  await userEvent.click(page.getByRole('button', { name: /increment/i }));

  await expect.element(page.getByText('1')).toBeInTheDocument();
});

In-source testing pattern

源码内测试模式

typescript
export function sum(a: number, b: number) {
  return a + b;
}

if (import.meta.vitest) {
  const { it, expect } = import.meta.vitest;

  it('adds numbers', () => {
    expect(sum(1, 2)).toBe(3);
  });
}
typescript
export function sum(a: number, b: number) {
  return a + b;
}

if (import.meta.vitest) {
  const { it, expect } = import.meta.vitest;

  it('数字相加', () => {
    expect(sum(1, 2)).toBe(3);
  });
}

Dependencies

依赖项

Required

必需

PackageVersionPurpose
vitest
^4Test runner and assertion/mocking APIs
vite
^6Shared Vite-powered module pipeline
node
>=20Required runtime for Vitest 4
版本用途
vitest
^4测试运行器及断言/Mocking API
vite
^6共享Vite驱动的模块管道
node
>=20Vitest 4所需的运行时

Optional

可选

PackageVersionPurpose
@vitest/coverage-v8
^4Fast, accurate coverage with AST remapping
@vitest/coverage-istanbul
^4Istanbul coverage backend
@vitest/browser-playwright
^4Playwright provider for Browser Mode
@vitest/browser-webdriverio
^4WebdriverIO provider for Browser Mode
@vitest/browser-preview
^4Preview provider for Browser Mode
版本用途
@vitest/coverage-v8
^4基于AST重映射的快速准确覆盖率工具
@vitest/coverage-istanbul
^4Istanbul覆盖率后端
@vitest/browser-playwright
^4浏览器模式的Playwright提供商
@vitest/browser-webdriverio
^4浏览器模式的WebdriverIO提供商
@vitest/browser-preview
^4浏览器模式的预览提供商

Official Documentation

官方文档

Troubleshooting

故障排查

Agent run hangs forever

Agent运行永久挂起

Symptoms: The test process never exits or Claude waits for additional file changes.
Solution:
bash
vitest run
症状: 测试进程永不退出,或Claude等待额外的文件更改。
解决方案:
bash
vitest run

Browser Mode test cannot spy on export

浏览器模式测试无法对导出进行Spy

Symptoms:
vi.spyOn()
throws, does nothing, or works in Node mode but fails in browser.
Solution:
typescript
vi.mock(import('./module'), { spy: true });
症状:
vi.spyOn()
抛出错误、无效果,或在Node模式下正常但在浏览器模式下失败。
解决方案:
typescript
vi.mock(import('./module'), { spy: true });

Coverage misses source files with no tests

覆盖率报告遗漏无测试的源文件

Symptoms: The report only contains files touched by executed tests.
Solution:
typescript
coverage: {
  provider: 'v8',
  include: ['src/**/*.{ts,tsx}'],
}
症状: 报告仅包含被执行测试触及的文件。
解决方案:
typescript
coverage: {
  provider: 'v8',
  include: ['src/**/*.{ts,tsx}'],
}

Legacy worker or pool settings break after upgrade

升级后旧版Worker或池设置失效

Symptoms: Old
maxThreads
,
maxForks
,
singleThread
,
singleFork
, or
poolOptions
settings stop working after moving to Vitest 4.
Solution:
typescript
test: {
  maxWorkers: 4,
}
Why: Vitest 4 simplified worker configuration and removed several older pool-specific options.
症状: 迁移到Vitest 4后,旧版的
maxThreads
maxForks
singleThread
singleFork
poolOptions
设置停止工作。
解决方案:
typescript
test: {
  maxWorkers: 4,
}
原因: Vitest 4简化了Worker配置,并移除了多个旧版池专属选项。

Setup Checklist

配置检查清单

Before using this skill, verify:
  • Node.js is
    >=20
  • vite
    is
    >=6
  • vitest.config.ts
    or
    vite.config.ts
    contains a
    test
    block
  • Agent/CI commands use
    vitest run
  • Coverage provider packages are installed if coverage is enabled
  • Browser provider packages are installed if Browser Mode is enabled
使用本技能前,请验证:
  • Node.js版本
    >=20
  • vite
    版本
    >=6
  • vitest.config.ts
    vite.config.ts
    包含
    test
    配置块
  • Agent/CI命令使用
    vitest run
  • 如果启用了覆盖率,已安装覆盖率提供商包
  • 如果启用了浏览器模式,已安装浏览器提供商包