Vitest 是一个由 Vite 驱动的下一代单元测试框架,旨在提供一个快速、现代且极具效率的测试体验。它与 Vite 深度集成,共享相同的配置、转换和解析器,从而为使用 Vite 构建的前端项目提供了无缝的测试解决方案。Vitest 的诞生,部分是为了解决传统前端测试工具(如 Jest)在大型项目中启动慢、HMR(热模块替换)支持不足等痛点。

核心思想:Vitest 利用 Vite 的 ESM-first 开发服务器和闪电般的 HMR 能力,为 JavaScript/TypeScript 项目带来前所未有的快速测试体验,尤其适合基于 Vite 的现代前端项目。


一、为什么选择 Vitest?

在前端开发日益复杂的今天,测试是保证代码质量和项目稳定性的关键环节。传统的测试框架,如 Jest,尽管功能强大,但在面对现代前端构建工具(如 ESM、TypeScript、JSX/TSX 转换)时,往往需要额外的配置和转换步骤,导致测试启动慢、HMR 效率不高。Vitest 应运而生,旨在解决这些问题。

1.1 核心优势

  1. Vite 驱动,极致速度

    • 直接利用 Vite 的 ESM-first 开发服务器和原生的浏览器 ESM 支持。
    • 瞬间启动:测试环境启动速度极快,无需复杂的捆绑过程。
    • 闪电 HMR:支持针对测试文件的热模块替换,当你修改测试文件或源文件时,只有相关的测试会重新运行,反馈几乎是即时的。
    • 按需编译:只编译测试所需的部分,而非整个项目。
  2. 配置简单,开箱即用

    • 与 Vite 共享配置:如果你已经在使用 Vite,Vitest 的配置与你的 vite.config.js 高度兼容,甚至可以直接使用。
    • 零配置起步:对于简单的项目,几乎无需配置即可开始测试。
    • 对 TypeScript / JSX / TSX / Vue / Svelte 等原生支持,无需额外配置 Preprocessor。
  3. 兼容性强,类 Jest API

    • Vitest 的 API 设计与 Jest 高度相似,这意味着如果你熟悉 Jest,几乎可以无缝迁移到 Vitest。
    • 支持 expect 断言库、describe / it 测试套件、beforeEach / afterEach 钩子等。
  4. 强大功能,全面覆盖

    • Mocking 系统:提供强大的模块和函数 Mocking 能力,方便隔离测试。
    • Snapshot Testing (快照测试):可以对组件渲染输出或数据结构进行快照比对,检测意外变更。
    • Code Coverage (代码覆盖率):内置支持 Istanbul/V8 覆盖率报告,无需额外配置。
    • In-source Testing (源内测试):可以在源文件内部直接编写测试,有利于开发-测试一体化。
    • Workspace 支持:可以在 Monorepo 中高效测试。
  5. 跨平台运行:支持 Node.js、浏览器环境(jsdom 或 happy-dom),甚至支持 Web Workers 和 Electron。

1.2 Vitest 与 Jest 的对比

特性 Vitest Jest
构建工具 Vite Rollup/Babel (内部包装,或需要额外配置)
启动速度 极快,得益于 Vite 的 ESM-first 相对较慢,需要 Babel/Webpack 转换
热模块替换 支持 HMR,实时反馈 通常需要重启整个测试进程,无原生 HMR 支持
配置 零配置起步,与 Vite 配置无缝集成 通常需要 Babel 配置,可能需要 jest.config.js
TypeScript 原生支持,无需额外配置 需要 ts-jest 或 Babel 插件进行配置
Mocking 功能强大,支持模块和函数 Mock 功能强大,API 类似
API 类 Jest API,学习成本低 业界标准,广泛使用
生态系统 新兴,发展迅速,与 Vite 生态结合紧密 成熟,生态系统庞大,工具有限性
执行环境 Node.js (默认), JSDOM/Happy-DOM, Browser Node.js (默认), JSDOM

二、安装与使用

2.1 安装

推荐与你的项目一起安装 Vitest。

前提:你的项目已经安装了 Vite。

1
2
3
4
5
6
7
8
# 使用 npm
npm install -D vitest

# 使用 yarn
yarn add -D vitest

# 使用 pnpm
pnpm add -D vitest

2.2 配置 package.json

package.jsonscripts 中添加测试命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"name": "my-vite-app",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest", // 运行一次所有测试
"test:watch": "vitest watch", // 监听文件变化并运行测试
"test:ui": "vitest --ui", // 启动 Vitest UI (可选)
"test:cov": "vitest run --coverage" // 运行测试并生成覆盖率报告
},
"devDependencies": {
"vite": "^ 最新版本",
"vitest": "^ 最新版本",
"@vitest/ui": "^ 最新版本", // 可选,用于测试 UI 界面
"@vitejs/plugin-vue": "^ 最新版本", // 如果是 Vue 项目
"happy-dom": "^ 最新版本", // 可选,更快且轻量级的 DOM 模拟
"@testing-library/vue": "^ 最新版本" // 如果是 Vue 组件测试
}
}

2.3 编写第一个测试

在你的项目根目录或 src 目录下创建一个 unit/__tests__/ 目录,并在其中创建测试文件。Vitest 默认会查找 .test.js, .test.ts, .spec.js, .spec.ts 等后缀的文件。

示例:src/utils/math.ts

1
2
3
4
5
6
7
8
9
10
11
12
// src/utils/math.ts
export function add(a: number, b: number): number {
return a + b;
}

export function subtract(a: number, b: number): number {
return a - b;
}

export function multiply(a: number, b: number): number {
return a * b;
}

示例:src/__tests__/math.test.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/__tests__/math.test.ts
import { describe, it, expect } from 'vitest';
import { add, subtract, multiply } from '../utils/math';

describe('Math functions', () => {
it('should correctly add two numbers', () => {
expect(add(1, 2)).toBe(3);
expect(add(-1, 1)).toBe(0);
expect(add(0, 0)).toBe(0);
});

it('should correctly subtract two numbers', () => {
expect(subtract(5, 2)).toBe(3);
expect(subtract(2, 5)).toBe(-3);
});

it('should correctly multiply two numbers', () => {
expect(multiply(2, 3)).toBe(6);
expect(multiply(0, 10)).toBe(0);
expect(multiply(-2, 3)).toBe(-6);
});
});

2.4 运行测试

1
2
3
npm test         # 运行一次所有测试
npm run test:watch # 监听模式,实时刷新
npm run test:ui # 启动 Vitest UI (浏览器界面)

三、Vitest 的核心功能与指令详解

3.1 测试运行器和 CLI 指令

  • vitest:默认运行所有测试一次并退出。
  • vitest watch (或 vitest --watch):监听模式,当测试文件或源文件发生变化时,只会重新运行受影响的测试。
  • vitest run:运行所有测试一次并退出,不带 watch 模式。在 CI/CD 环境中常用。
  • vitest [file-pattern]:只运行指定文件。例如 vitest src/components/Button.test.vue
  • vitest --ui:启动一个基于浏览器的 Vitest UI 界面,提供更友好的测试结果展示、过滤器和调试功能。
  • vitest --coverage:运行测试并生成代码覆盖率报告。
  • vitest --shard=1/3:在多机器并行测试时,将测试分片。
  • vitest --browser --playwright:在真实浏览器中运行测试(需要配置)。

3.2 断言库 (expect)

Vitest 默认使用 expect 作为断言库,其 API 与 Jest 几乎完全一致。

常见断言

  • expect(value).toBe(expected):严格相等 (===)。
  • expect(value).toEqual(expected):深度相等(比较对象和数组的内容)。
  • expect(value).toBeTruthy() / expect(value).toBeFalsy():检查布尔值。
  • expect(value).toThrow() / toThrow('message'):断言函数抛出错误。
  • expect(array).toContain(item):数组包含某个元素。
  • expect(string).toMatch(/regex/):字符串匹配正则表达式。
  • expect(mockFn).toHaveBeenCalledTimes(n):Mock 函数被调用次数。
  • expect(mockFn).toHaveBeenCalledWith(...args):Mock 函数被调用时传入的参数。
  • expect(value).not.toBe(expected):反向断言。

3.3 测试套件 (describe) 与 测试用例 (it/test)

  • describe(name, fn):定义一个测试套件,用于组织相关的测试用例。
  • it(name, fn) / test(name, fn):定义一个具体的测试用例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { describe, it } from 'vitest';

describe('A suite of tests', () => {
it('should do something correctly', () => {
// ... 断言
});

// 可以嵌套 describe
describe('Nested suite', () => {
it('should handle edge cases', () => {
// ...
});
});
});

3.4 钩子函数 (beforeEach, afterEach, beforeAll, afterAll)

用于在测试套件或用例运行前后执行设置或清理操作。

  • beforeAll(fn):在当前 describe 块内的所有测试用例开始前执行一次。
  • afterAll(fn):在当前 describe 块内的所有测试用例结束后执行一次。
  • beforeEach(fn):在当前 describe 块内的每个测试用例开始前执行一次。
  • afterEach(fn):在当前 describe 块内的每个测试用例结束后执行一次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { describe, it, beforeEach, afterEach } from 'vitest';

let data = [];

describe('Data operations', () => {
// 在每个测试用例前重置数据
beforeEach(() => {
data = [1, 2, 3];
});

it('should add an item', () => {
data.push(4);
expect(data).toEqual([1, 2, 3, 4]);
});

it('should remove an item', () => {
data.pop();
expect(data).toEqual([1, 2]);
});
});

3.5 Mocking

Vitest 提供了强大的 Mocking 功能,用于隔离被测模块的依赖。

  • 函数 Mockvi.fn()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import { vi, it, expect } from 'vitest';

    it('should call a mock function', () => {
    const mockFn = vi.fn((a, b) => a + b);
    mockFn(1, 2);
    expect(mockFn).toHaveBeenCalledTimes(1);
    expect(mockFn).toHaveBeenCalledWith(1, 2);
    expect(mockFn.mock.results[0].value).toBe(3);
    });
  • 模块 Mockvi.mock('module-name')

    • vi.mock('axios', () => ({ default: { get: vi.fn() } })):完全 mock 模块。
    • vi.importActual('module-name'):获取模块的实际导出,然后部分 mock。
    • vi.spyOn(object, 'methodName'):监视对象上的方法调用而不改变其实现。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // src/api.ts
    import axios from 'axios';
    export const fetchUserData = (id: number) => axios.get(`/users/${id}`);

    // src/__tests__/api.test.ts
    import { vi, it, expect } from 'vitest';
    import { fetchUserData } from '../api';
    import axios from 'axios'; // 导入 actual axios 实例

    // 完整 mock axios 模块
    vi.mock('axios', () => ({
    default: {
    get: vi.fn(() => Promise.resolve({ data: { id: 1, name: 'Mocked User' } })),
    },
    }));

    it('should fetch user data', async () => {
    const result = await fetchUserData(1);
    expect(axios.get).toHaveBeenCalledWith('/users/1');
    expect(result.data).toEqual({ id: 1, name: 'Mocked User' });
    });

3.6 Snapshot Testing (快照测试)

捕捉组件的渲染输出或数据结构,并存储为快照。下次运行时,将结果与快照进行比较。

  • expect(value).toMatchSnapshot():生成内联快照。
  • expect(value).toMatchInlineSnapshot():生成文件快照。
1
2
3
4
5
6
7
8
9
10
// src/__tests__/component.test.ts (假设你在测试一个 Vue/React 组件)
import { it, expect } from 'vitest';
// 假设这是你的组件渲染结果
const componentOutput = `<div class="card"><h1>Test Component</h1><p>Hello Vitest!</p></div>`;

it('component should render correctly', () => {
// 首次运行会生成快照文件或内联快照
// 后续运行会与快照进行比对
expect(componentOutput).toMatchSnapshot();
});

3.7 Code Coverage (代码覆盖率)

Vitest 内置支持代码覆盖率报告,通过 C8 (默认) 或 Istanbul。

1
2
3
npm run test:cov
# 或者
vitest run --coverage

这会在 .vitest-cache/coverage/ (默认) 下生成覆盖率报告,通常可以在浏览器中打开 html/index.html 查看详细报告。

3.8 In-source Testing (源内测试)

允许在源文件内部编写测试。这有利于将测试与业务逻辑更紧密地结合,但可能会使源文件显得不那么干净。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/utils/square.ts
export function square(num: number): number {
return num * num;
}

// 在同一个文件内编写测试
if (import.meta.vitest) {
const { it, expect } = import.meta.vitest;
it('square', () => {
expect(square(2)).toBe(4);
expect(square(0)).toBe(0);
expect(square(-3)).toBe(9);
});
}

四、高级配置与集成

4.1 vite.config.ts (或 vitest.config.ts)

Vitest 默认可以读取您的 vite.config.ts 文件。您也可以创建独立的 vitest.config.ts 来存放 Vitest 特有的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// vite.config.ts 或 vitest.config.ts
import { defineConfig } from 'vitest/config';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
plugins: [vue()],
test: {
environment: 'happy-dom', // 模拟浏览器环境
globals: true, // 使 describe, it, expect 等全局可用,无需导入
setupFiles: './vitest.setup.ts', // 全局设置文件
include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], // 包含的测试文件模式
exclude: ['**/node_modules/**', '**/dist/**', './**/cypress/**', '**/.{idea,git,cache,output,temp}/**'], // 排除的文件模式
coverage: {
provider: 'v8', // 或者 'istanbul'
reporter: ['text', 'json', 'html'], // 报告格式
tempDirectory: './.vitest-cache/coverage', // 覆盖率临时文件目录
include: ['src/**/*.{js,ts,vue,jsx,tsx}'], // 统计覆盖率的文件
exclude: ['src/**/*.d.ts', 'src/**/__tests__/**'], // 排除覆盖率文件
},
threads: true, // 启用多线程并行测试
watch: true, // 默认开启 watch 模式
reporters: ['default', 'junit'], // 自定义报告器
},
});

常用配置项解释

  • environment:指定测试运行环境。
    • node:默认,Node.js 环境。
    • jsdom (或 happy-dom):模拟浏览器 DOM 环境,用于测试 UI 组件。happy-domjsdom 更快、更轻量。
  • globals:设置为 true 后,describe, it, expect 等无需手动导入,可在所有测试文件中直接使用(类似于 Jest 的默认行为)。
  • setupFiles:指定在所有测试开始前运行的全局设置文件,可用于配置测试环境、注册全局 Mock 等。
  • include/exclude:用于控制 Vitest 扫描哪些文件作为测试文件,排除哪些文件。
  • coverage:配置代码覆盖率报告。
    • provider: 覆盖率工具,v8 (默认) 或 istanbul
    • reporter: 报告格式,如 text, html, json 等。
    • include/exclude: 用于指定哪些文件需要统计覆盖率。
  • threads:是否使用多线程并行运行测试。
  • watch:是否默认开启 watch 模式。

4.2 全局设置文件 (vitest.setup.ts)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// vitest.setup.ts
import { beforeAll, afterAll } from 'vitest';
import { cleanup } from '@testing-library/vue'; // 假设是 Vue 项目

// 可以在这里进行全局的 mock,例如 mock fetch 或 localStorage
// vi.stubGlobal('localStorage', {
// getItem: vi.fn(),
// setItem: vi.fn(),
// });

beforeAll(() => {
console.log('Global setup: Tests start!');
});

afterAll(() => {
console.log('Global teardown: Tests end!');
});

// 如果使用 @testing-library,通常需要在每次测试后进行清理
afterEach(() => {
cleanup(); // 清理 DOM
});

4.3 UI 组件测试集成

对于 Vue、React、Svelte 等组件库,Vitest 可以与 @testing-library 等工具完美结合。

示例 (Vue 组件测试)

1
npm install -D @vue/test-utils @testing-library/vue happy-dom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/components/Counter.vue
<script setup lang="ts">
import { ref } from 'vue';
const count = ref(0);
const increment = () => count.value++;
const decrement = () => count.value--;
</script>

<template>
<div>
<button @click="decrement">-</button>
<span>{{ count }}</span>
<button @click="increment">+</button>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/components/Counter.test.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';

describe('Counter component', () => {
it('should render correctly', () => {
const wrapper = mount(Counter);
expect(wrapper.text()).toContain('0');
expect(wrapper.html()).toMatchSnapshot(); // 快照测试
});

it('should increment count on button click', async () => {
const wrapper = mount(Counter);
await wrapper.findAll('button')[1].trigger('click'); // 点击 + 按钮
expect(wrapper.text()).toContain('1');
});

it('should decrement count on button click', async () => {
const wrapper = mount(Counter);
await wrapper.findAll('button')[0].trigger('click'); // 点击 - 按钮
expect(wrapper.text()).toContain('-1');
});
});

五、总结

Vitest 作为一个由 Vite 驱动的测试框架,为现代前端开发带来了革命性的改变。它通过利用 Vite 的 ESM 编译和 HMR 能力,提供了前所未有的测试速度和开发体验。其与 Jest 相似的 API 设计,使得开发者迁移成本极低,同时又提供了强大的 Mocking、快照测试、代码覆盖率等功能。

无论是新项目还是从 Jest 迁移的现有项目,Vitest 都提供了一个优秀的测试解决方案,让开发者能够以更快的速度编写、运行和迭代测试,从而有效提升前端项目的质量和开发效率。随着前端生态的不断发展,Vitest 有望成为现代前端测试领域的首选工具。