Skip to content

Vitest

简介

Vitest 是一个由 Vite 提供支持的极速单元测试框架,专为现代前端项目设计。它利用 Vite 的开发服务器和 HMR(热模块替换)能力,提供了极快的测试执行速度和优秀的开发体验。Vitest 在 API 设计上与 Jest 兼容,同时提供了更好的性能和更现代的特性。

核心特性

极速执行

Vitest 利用 Vite 的按需编译和原生 ESM 支持,实现了极快的测试启动和执行速度:

bash
# 安装 Vitest
npm install -D vitest

# 添加测试脚本到 package.json
{
  "scripts": {
    "test": "vitest",
    "test:ui": "vitest --ui",
    "test:run": "vitest run"
  }
}

# 运行测试
npm test

智能监视模式

Vitest 默认以监视模式运行,只重新运行受影响的测试,大大提高了开发效率:

bash
# 启动监视模式
npm test

# 运行一次后退出
npm test:run

内置 UI 界面

Vitest 提供了一个现代化的 Web UI 界面,可以可视化测试结果和覆盖率报告:

bash
npm test:ui

基本用法

编写测试

javascript
// sum.js
export function sum(a, b) {
  return a + b;
}

// sum.test.js
import { expect, test } from 'vitest';
import { sum } from './sum';

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

测试组织

javascript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';

describe('计算器功能测试', () => {
  beforeEach(() => {
    // 每个测试前的设置
  });

  afterEach(() => {
    // 每个测试后的清理
  });

  it('加法运算', () => {
    expect(sum(1, 2)).toBe(3);
  });

  it('减法运算', () => {
    expect(subtract(5, 2)).toBe(3);
  });
});

常用匹配器

Vitest 提供了与 Jest 兼容的匹配器 API:

javascript
// 精确匹配
expect(value).toBe(2);  // 使用 Object.is 比较
expect(value).toEqual({ a: 1 });  // 递归比较对象的值

// 真值检查
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();

// 数字比较
expect(value).toBeGreaterThan(3);
expect(value).toBeLessThanOrEqual(5);

// 字符串
expect('team').toMatch(/tea/);

// 数组
expect(['apple', 'banana']).toContain('apple');

// 异常
expect(() => { throw new Error('错误') }).toThrow();

高级功能

异步测试

javascript
// Promise
test('异步数据获取', () => {
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});

// Async/Await
test('异步数据获取', async () => {
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});

测试覆盖率

Vitest 内置了基于 c8/v8 的代码覆盖率工具:

bash
# 运行测试并收集覆盖率
npm test -- --coverage
javascript
// vitest.config.js
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    coverage: {
      provider: 'c8', // 或 'istanbul'
      reporter: ['text', 'json', 'html'],
      exclude: [
        'node_modules/',
        'test/'
      ],
      lines: 90,
      functions: 90,
      branches: 90,
      statements: 90
    }
  }
});

测试 Vue 组件

javascript
import { mount } from '@vue/test-utils';
import { describe, it, expect } from 'vitest';
import Counter from '../Counter.vue';

describe('Counter.vue', () => {
  it('increments count when button is clicked', async () => {
    const wrapper = mount(Counter);
    
    expect(wrapper.text()).toContain('Count: 0');
    await wrapper.find('button').trigger('click');
    expect(wrapper.text()).toContain('Count: 1');
  });
});

测试 React 组件

javascript
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import Counter from '../Counter';

describe('Counter', () => {
  it('increments count when button is clicked', () => {
    render(<Counter />);
    
    expect(screen.getByText('Count: 0')).toBeInTheDocument();
    fireEvent.click(screen.getByRole('button'));
    expect(screen.getByText('Count: 1')).toBeInTheDocument();
  });
});

配置选项

vitest.config.js

javascript
import { defineConfig } from 'vitest/config';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  test: {
    // 测试环境
    environment: 'jsdom',
    
    // 包含的文件
    include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
    
    // 排除的文件
    exclude: ['**/node_modules/**', '**/dist/**'],
    
+    
    // 全局测试设置
    globals: true,
    
    // 测试超时时间(毫秒)
    testTimeout: 5000,
    
    // 并行或串行运行
    threads: true,
    
    // 模拟浏览器环境
    deps: {
      inline: ['element-plus']
    },
    
    // 别名
    alias: {
      '@': '/src'
    }
  }
});

与 Vite 集成

Vitest 可以直接使用 Vite 的配置,包括插件、别名和转换器:

javascript
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': '/src'
    }
  },
  // Vitest 会自动使用这些配置
});

最佳实践

测试组织

  • 使用 describe 对相关测试进行分组
  • 使用 beforeEachafterEach 设置和清理测试环境
  • 利用 it.skipit.todo 管理测试进度

性能优化

  • 启用多线程执行:--threads
  • 使用 --bail 在首次失败后停止
  • 利用 --update 自动更新快照

Mock 策略

  • 使用 vi.mock() 模拟模块
  • 使用 vi.fn() 创建模拟函数
  • 使用 vi.spyOn() 监视对象方法
javascript
import { vi } from 'vitest';

// 模拟函数
const mockFn = vi.fn();
mockFn.mockReturnValue(42);

// 模拟模块
vi.mock('./math', () => {
  return {
    sum: vi.fn().mockReturnValue(10)
  };
});

// 监视方法
const spy = vi.spyOn(console, 'log');

常见问题与解决方案

与 Jest 的兼容性

javascript
// vitest.config.js
export default {
  test: {
    globals: true,  // 启用全局 API,无需导入
    environment: 'jsdom',  // 使用 jsdom 环境
    setupFiles: ['./setup.js']  // 设置文件,类似 Jest 的 setupFilesAfterEnv
  }
};

模块路径解析

javascript
// vitest.config.js
import { defineConfig } from 'vitest/config';
import { resolve } from 'path';

export default defineConfig({
  resolve: {
    alias: {
      '@': resolve(__dirname, './src')
    }
  }
});

测试环境问题

bash
# 安装 jsdom 环境
npm install -D @vitest/environment-jsdom

# 安装 happy-dom 环境(更轻量的替代品)
npm install -D happy-dom
javascript
// vitest.config.js
export default {
  test: {
    environment: 'jsdom',  // 或 'happy-dom', 'node'
  }
};

与其他测试工具对比

特性VitestJestCypress
执行速度极快中等
Vite 集成原生需配置需配置
配置复杂度中等
内置 UI支持不支持支持
组件测试优秀良好优秀
热更新支持有限不支持
社区生态成长中丰富丰富

学习资源