Vitest单元测试教程

Vitest 是 Vite 生态的极速单元测试框架,API 兼容 Jest,上手快、配置简单、性能极高。下面从 安装 → 配置 → 编写测试 → 常用断言 → Mock → 组件测试 → 运行与覆盖率 完整流程带你上手。

一、安装

1. 基础安装

bash 复制代码
npm i -D vitest
# 或
yarn add -D vitest
pnpm add -D vitest

2. 常用依赖(按需)

  • 浏览器环境模拟 (前端组件/DOM 测试)

    bash 复制代码
    npm i -D jsdom happy-dom
  • Vue 组件测试

    bash 复制代码
    npm i -D @vue/test-utils
  • React 组件测试

    bash 复制代码
    npm i -D @testing-library/react @testing-library/jest-dom
  • 测试覆盖率

    bash 复制代码
    npm i -D @vitest/coverage-v8
  • UI 界面

    bash 复制代码
    npm i -D @vitest/ui

二、配置(vite.config.ts / vitest.config.ts)

typescript 复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    // 全局注入 API(describe/it/expect/vi)
    globals: true,
    // 测试环境:node | jsdom | happy-dom
    environment: 'jsdom',
    // 测试文件匹配规则
    include: ['src/**/*.{test,spec}.{js,ts,jsx,tsx,vue}'],
    // 覆盖率配置
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: [
        'node_modules/',
        'dist/',
        'src/main.ts',
        '**/*.d.ts'
      ]
    }
  }
})

package.json 脚本

json 复制代码
{
  "scripts": {
    "test": "vitest",               // 监听模式(开发常用)
    "test:run": "vitest run",       // 单次运行(CI)
    "test:ui": "vitest --ui",       // 可视化 UI
    "test:coverage": "vitest run --coverage" // 覆盖率
  }
}

三、编写第一个测试(工具函数)

1. 待测试代码:src/utils/math.ts

typescript 复制代码
export function sum(a: number, b: number) {
  return a + b
}

export function divide(a: number, b: number) {
  if (b === 0) throw new Error('除数不能为 0')
  return a / b
}

2. 测试文件:src/utils/math.test.ts

typescript 复制代码
import { describe, it, expect } from 'vitest'
import { sum, divide } from './math'

// 测试套件(分组)
describe('数学工具', () => {
  // 单个测试用例
  it('sum 1+2=3', () => {
    expect(sum(1, 2)).toBe(3)
  })

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

  it('divide 正常除法', () => {
    expect(divide(6, 2)).toBe(3)
  })

  // 测试异常
  it('divide 除以 0 抛出错误', () => {
    expect(() => divide(6, 0)).toThrow('除数不能为 0')
  })
})

四、常用断言(expect)

typescript 复制代码
// 基础匹配
expect(2+2).toBe(4)               // 严格相等(===)
expect({a:1}).toEqual({a:1})       // 对象深度相等
expect('abc').toContain('b')       // 包含
expect(null).toBeNull()
expect(undefined).toBeUndefined()
expect(0).toBeFalsy()
expect(1).toBeTruthy()

// 数字
expect(5).toBeGreaterThan(3)
expect(5).toBeLessThan(10)

// 异常
expect(() => fn()).toThrow('msg')

// 异步
await expect(promise).resolves.toBe('ok')
await expect(promise).rejects.toThrow('err')

五、Mock 模拟(vi)

1. 模拟函数

typescript 复制代码
import { vi, it, expect } from 'vitest'

const mockFn = vi.fn()
mockFn('a', 1)

expect(mockFn).toHaveBeenCalled()
expect(mockFn).toHaveBeenCalledWith('a', 1)
expect(mockFn).toHaveBeenCalledTimes(1)

// 模拟返回值
mockFn.mockReturnValue(42)
expect(mockFn()).toBe(42)

2. 模拟模块

typescript 复制代码
import { vi, it, expect } from 'vitest'
import { fetchData } from './api'

// 模拟整个模块
vi.mock('./api', () => ({
  fetchData: vi.fn().mockResolvedValue({ data: 'mock' })
}))

it('异步测试', async () => {
  const res = await fetchData()
  expect(res.data).toBe('mock')
  expect(fetchData).toHaveBeenCalledOnce()
})

3. 定时器模拟

typescript 复制代码
import { vi, it, expect } from 'vitest'

it('定时器测试', () => {
  vi.useFakeTimers()
  const fn = vi.fn()

  setTimeout(fn, 1000)
  // 快进时间
  vi.advanceTimersByTime(1000)

  expect(fn).toHaveBeenCalled()
  vi.useRealTimers()
})

六、Vue 组件测试示例

组件:src/components/Counter.vue

vue 复制代码
<template>
  <div>
    <p>count: {{ count }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => count.value++
</script>

测试:Counter.test.ts

typescript 复制代码
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'

describe('Counter 组件', () => {
  it('初始 count 为 0', () => {
    const wrapper = mount(Counter)
    expect(wrapper.text()).toContain('count: 0')
  })

  it('点击按钮 count +1', async () => {
    const wrapper = mount(Counter)
    await wrapper.find('button').trigger('click')
    expect(wrapper.text()).toContain('count: 1')
  })
})

七、运行测试

bash 复制代码
# 监听模式(修改文件自动重测)
npm test

# 单次运行
npm run test:run

# 可视化 UI(浏览器查看)
npm run test:ui

# 覆盖率报告(生成 coverage/ 目录)
npm run test:coverage

八、最佳实践

  1. 测试文件命名xxx.test.ts / xxx.spec.ts
  2. 结构describe 分组 → it/test 用例 → expect 断言
  3. 隔离 :每个用例独立,用 beforeEach/afterEach 重置状态
  4. 测行为不测实现:测输出/交互,不测内部变量
  5. 覆盖率:核心逻辑 ≥ 80%,不盲目追求 100%
相关推荐
许彰午14 天前
39_Java单元测试JUnit入门
java·junit·单元测试
果子耶耶14 天前
让大模型帮我写单元测试,5个模型的覆盖率和边界处理能力实测
chatgpt·单元测试
川石课堂软件测试15 天前
APP自动化测试|高级手势操作&toast操作
css·功能测试·测试工具·microsoft·fiddler·单元测试·harmonyos
Thecozzy17 天前
单元测试 vs 手工测试:以水印功能为例
单元测试
HLAIA光子18 天前
AI Coding框架,打好TDD和SDD这两拳
单元测试·ai编程·代码规范
霸道流氓气质18 天前
Java 单元测试生成大量 Excel 测试数据实战指南
java·单元测试·excel
川石课堂软件测试18 天前
UI自动化测试|下拉选择框&弹出框&滚动条操作实践
开发语言·python·jmeter·ui·docker·单元测试·harmonyos
川石课堂软件测试19 天前
UI自动化测试|元素操作&浏览器操作实践
功能测试·测试工具·mysql·ui·docker·容器·单元测试
无聊的老谢19 天前
电信系统中的单元测试策略:构建高可靠性的微服务防线
数据库·微服务·单元测试