Vitest — Vite 原生测试框架
是什么
Vitest 是一个与 vite 共用编译流水线、Jest API 兼容的测试框架。日常类比:
jest 像每次开车要先暖车 30 秒——它有自己一套独立的 babel-jest 转译器,跑测试前要把 TS / JSX / 静态资源全部重新处理一遍。
Vitest 是直接打火即走——开发用的 Vite dev server 已经把代码编译好了,跑测试就是再问一次同样的代码而已。
最简上手:
import { test, expect } from 'vitest'
test('add', () => { expect(1 + 1).toBe(2)})npx vitest # watch 模式(默认)npx vitest run # 一次跑完退出没有 babel.config.js、没有 jest.config.ts——你的 vite.config.ts 就是测试配置。
为什么重要
不理解 Vitest,下面这些事都没法解释:
- 为什么 Vue / Svelte / Solid / Astro 这类 Vite 项目 2024 年后默认推荐 Vitest——配置零成本
- 为什么 React 项目从 jest 大量迁出——启动比 Jest 快 5-10 倍,watch 模式只重跑改动的测试
- 为什么
describe / it / expect写法看起来和 Jest 一模一样——Vitest 故意保持 API 兼容,迁移成本极低 - 为什么”前端工具栈收敛”是 2020 后的趋势——Vitest 的哲学就是”测试是 Vite dev server 的一个 consumer”,而不是”独立王国”
一句话:Vitest 证明了前端工具不必各自维护一套 transpiler——共享上游能拿到所有红利。
核心要点
Vitest 的心智模型只有 三件事:
-
共享 Vite config:
resolve.alias/define/plugins/ 环境变量在测试时全部生效。类比:开发和测试是同一辆车的两个挡位,不是两辆车。 -
浏览器模式(experimental):传统测试用
jsdom模拟浏览器(慢、不全),Vitest browser mode 直接驱动 Playwright / WebDriver 在真浏览器里跑。组件单测的真实度跃升一个量级。 -
Snapshot / mock / coverage 内置:不用配
babel-jest、不用装@types/jest、不用拼nyc。expect(x).toMatchSnapshot()/vi.mock('./api')/vitest run --coverage开箱即用。
三件事加起来 ≈ 一个 vite.config.ts 加一行 test: {}。
实践案例
案例 1:最简单元测试
import { test, expect } from 'vitest'import { sum } from './sum'
test('1 + 2 = 3', () => { expect(sum(1, 2)).toBe(3)})npx vitest run逐部分解释:
import { test, expect } from 'vitest':显式 import,IDE 跳转 / type-check / tree-shaking 都更友好expect(...).toBe(...):API 与 Jest 完全一致,迁移时 sed 改 import 即可- 没有
vitest.config.ts——空配置直接跑
案例 2:Mock 一个 module
import { test, expect, vi } from 'vitest'import { fetchUser } from './api'import { greet } from './greet'
vi.mock('./api', () => ({ fetchUser: vi.fn(() => Promise.resolve({ name: 'Jason' })),}))
test('greet uses mocked fetchUser', async () => { const msg = await greet(1) expect(msg).toBe('Hello, Jason') expect(fetchUser).toHaveBeenCalledWith(1)})vi.mock 与 jest.mock 规则一致——必须写在文件顶部(被 esbuild 提升到所有 import 之前)。vi.fn() 创建带 spy 能力的假函数。
案例 3:Coverage 一行命令
npx vitest run --coverage默认 v8 provider,跑完在 coverage/ 目录生成 HTML 报告。open coverage/index.html 看每行命中情况。
要换 Istanbul:
export default defineConfig({ test: { coverage: { provider: 'istanbul' }, },})踩过的坑
-
jsdom vs happy-dom 选哪个:默认
jsdom(Mozilla 维护,慢但兼容性高);happy-dom快 2-3 倍但 API 覆盖不全(某些 DOM 边缘特性缺失)。组件库通常先 happy-dom 跑通,CI 关键路径再切 jsdom。 -
Mock hoisting 陷阱:
vi.mock(path, factory)会被 esbuild 提升到 import 之前。如果 factory 内部引用了还没 import 的变量,会报Cannot access before initialization。解决方式:用vi.hoisted(() => ...)显式提升变量。 -
React 18 Testing Library 配合:
@testing-library/react18+ 在 Vitest 里要确保environment: 'jsdom'且装@testing-library/jest-dom。canvas / WebGL 测试还要vitest-canvas-mock兜底。 -
globals: true选项:开了之后describe / it / expect自动全局可用(兼容 Jest 默认行为)。看起来方便,但失去 import 显式性,IDE 跳转 / type-check / tree-shaking 都更弱。新项目直接import { describe } from 'vitest',不要开 globals。 -
错误堆栈在 worker 里偶尔不准:source map 经过 Vite ModuleRunner 后再经过 IPC 传回主进程,少数情况下行号偏移 1-2。已知 issue,不致命但偶尔影响调试——加
--no-isolate跑可以暂时绕开。
适用 vs 不适用场景
适用:
- 任何已经在用 vite 的项目(Vue / Svelte / Solid / Astro / Vite + React)—— 配置零成本
- 从 jest 迁出的中大型项目——API 95% 兼容,sed 改 import 即可
- 需要在真浏览器跑组件单测——browser mode 比 jsdom 真实,比 e2e 轻
- TypeScript 项目——不用配
ts-jest/babel-jest,esbuild 自动转译
不适用:
- 不用 vite 的项目(webpack 5 stack)—— 引入 Vitest 等于多装一份 vite,得不偿失
- Node 库纯零依赖测试 —— 用 Node 22+ 内建
node:test,启动 ~30ms(Vitest 是 ~300ms) - 端到端浏览器测试 —— 用 playwright test,单元测试不是它的赛道
- Bun 全栈项目 —— 用
bun:test,启动 ~50ms(绑死 bun runtime 但更快)
历史小故事(可跳过)
- 2021 年:Anthony Fu(antfu)启动 Vitest 项目,目标是”让 Vite 项目跑测试不用切 Jest”。
- 2022 年:Vitest 1.0 发布。VoidZero(Vite 母公司)全职雇员 sheremet-va 接手主要维护。
- 2024 年:Stack Overflow 调查显示 Vitest 在前端测试框架中用户量逼近 Jest,Vue / Svelte / Solid / Astro 默认推荐。
- 2026 年:v3 当家,浏览器模式从 experimental 走向 stable。
3 年时间从”小众替代品”到”事实标准”。
学到什么
- 测试是开发工具栈的一个 consumer,不是独立王国——这是 Vitest 比 jest 哲学更先进的核心。共享上游 = 共享所有红利。
- API 兼容是最廉价的迁移利器——Vitest 故意复刻 Jest 的
describe / it / expect / mock命名,让用户”无脑迁移”。这是工程胜于创新的典范。 - Watch 模式的本质是”判定哪些 spec 受影响 → 重跑”,不是”原 worker 内 HMR 替换模块”。前者是 Vitest 的,后者是 Vite dev server 的——两件事别混。
- 隔离粒度可调:
forks(默认,进程隔离)/threads(worker_threads,快但脆)/vmForks/vmThreads(每 spec 新 vm.Context,最强隔离最慢)。多数项目默认就够,不要无脑提升强度。
延伸阅读
- 官方 docs:vitest.dev(30 分钟读完核心 API)
- 视频教程:Anthony Fu — Vitest 1.0 release talk(作者亲自讲设计哲学)
- 自己写实现:照着
packages/vitest/src/node/core.ts读 Vitest 启动流程,再对照pools/workers/forksWorker.ts看 worker fork 实现——能讲清楚 Vitest 内核就懂了大半测试框架设计 - vite —— Vitest 的上游,理解 Vite dev server 才能理解 Vitest watch
- jest —— 前一代标准,Vitest 的对比基准
- playwright —— 端到端测试标配,Vitest browser mode 的后端选择之一
关联
- vite —— Vitest 直接复用 Vite 的 transpile / ModuleRunner / watcher,理解 Vite 是理解 Vitest 的前提
- jest —— Vitest API 兼容的目标,迁移时一对照就懂
- playwright —— Vitest browser mode 默认后端;纯 e2e 仍用 Playwright Test
- esbuild —— Vite 内部的转译器,Vitest 间接复用——这是它跑得快的根因
- bun ——
bun:test是 Vitest 的新挑战者(绑死 bun runtime 换更快启动)
反向链接
- bun —— Bun — JS 全能运行时
- esbuild —— esbuild — 用 Go 写的极速 JS bundler
- jest —— Jest — 一个包就能跑 JS 测试的全家桶
- midscene —— midscene — 用自然语言代替 selector 的浏览器自动化框架
- msw —— MSW — 让 mock 不改业务代码,在网络层透明拦截
- playwright —— Playwright — 跨浏览器自动化测试
- storybook —— Storybook — 给 UI 组件的独立工作台
- testing-library —— Testing Library — 像用户一样测前端,重构不再挂测试
- vite —— Vite — 浏览器自己加载源码的构建工具
- vue —— Vue.js — 渐进式 UI 框架
- webpack —— webpack 模块打包