Vitest 高级特性 #

浏览器模式 #

概述 #

Vitest 支持在真实浏览器环境中运行测试,适合测试 DOM 操作、Web API、组件交互等场景。

text
┌─────────────────────────────────────────────────────────────┐
│                    浏览器模式架构                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐    │
│  │ Vitest CLI  │ -> │  Playwright │ -> │   Browser   │    │
│  │             │    │  / Webdriver│    │  Chrome/FF  │    │
│  └─────────────┘    └─────────────┘    └─────────────┘    │
│         │                  │                  │            │
│         │                  │                  │            │
│         └──────────────────┴──────────────────┘            │
│                       WebSocket 通信                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

安装依赖 #

bash
# 安装浏览器模式提供者
npm install -D @vitest/browser

# 安装浏览器驱动(选择一个)
npm install -D playwright
# 或
npm install -D webdriverio

配置浏览器模式 #

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

export default defineConfig({
  test: {
    browser: {
      enabled: true,
      name: 'chrome', // 'chrome' | 'firefox' | 'safari' | 'edge'
      provider: 'playwright', // 'playwright' | 'webdriverio'
      headless: true,
    },
  },
})

编写浏览器测试 #

typescript
// button.test.ts
import { expect, test } from 'vitest'
import { page } from '@vitest/browser/context'

test('button click', async () => {
  // 渲染组件
  document.body.innerHTML = `
    <button id="myButton">Click me</button>
  `
  
  const button = document.getElementById('myButton')
  
  // 点击按钮
  button?.click()
  
  // 验证结果
  expect(button?.textContent).toBe('Click me')
})

test('DOM interaction', async () => {
  document.body.innerHTML = `
    <input id="myInput" type="text" />
    <span id="result"></span>
  `
  
  const input = document.getElementById('myInput') as HTMLInputElement
  const result = document.getElementById('result')
  
  // 模拟输入
  input.value = 'Hello'
  input.dispatchEvent(new Event('input'))
  
  // 验证
  expect(input.value).toBe('Hello')
})

使用 Playwright 工具 #

typescript
import { expect, test } from 'vitest'
import { page } from '@vitest/browser/context'

test('using playwright', async () => {
  // 使用 Playwright API
  await page.goto('http://localhost:3000')
  
  // 查找元素
  const button = page.locator('button')
  await button.click()
  
  // 验证
  await expect(button).toHaveText('Clicked')
})

工作区 #

概述 #

工作区允许在单个项目中管理多个测试配置,适合 monorepo 或多环境项目。

text
project/
├── packages/
│   ├── core/
│   │   ├── src/
│   │   ├── test/
│   │   └── vitest.config.ts
│   └── ui/
│       ├── src/
│       ├── test/
│       └── vitest.config.ts
├── vitest.workspace.ts
└── package.json

配置工作区 #

typescript
// vitest.workspace.ts
import { defineWorkspace } from 'vitest/config'

export default defineWorkspace([
  'packages/core/vitest.config.ts',
  'packages/ui/vitest.config.ts',
  {
    test: {
      name: 'integration',
      include: ['tests/**/*.test.ts'],
      environment: 'node',
    },
  },
])

包级别配置 #

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

export default defineConfig({
  test: {
    name: 'core',
    include: ['test/**/*.test.ts'],
    environment: 'node',
  },
})

// packages/ui/vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    name: 'ui',
    include: ['test/**/*.test.ts'],
    environment: 'jsdom',
  },
})

运行工作区测试 #

bash
# 运行所有工作区测试
vitest

# 运行特定项目
vitest --project=core
vitest --project=ui

In-Source Testing #

概述 #

In-Source Testing 允许在源代码文件中直接编写测试,无需创建单独的测试文件。

基本用法 #

typescript
// utils.ts
export function add(a: number, b: number): number {
  return a + b
}

// 在源文件中编写测试
if (import.meta.vitest) {
  const { test, expect } = import.meta.vitest
  
  test('add', () => {
    expect(add(1, 2)).toBe(3)
  })
}

配置 #

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

export default defineConfig({
  test: {
    includeSource: ['src/**/*.{ts,js}'],
  },
})

优势 #

  • 测试与实现代码放在一起
  • 更容易保持测试和实现同步
  • 适合小型工具函数

自定义报告器 #

创建自定义报告器 #

typescript
// custom-reporter.ts
import type { Reporter, TestCase, TestSuite } from 'vitest'

export default class CustomReporter implements Reporter {
  onInit() {
    console.log('Test run starting...')
  }
  
  onFinished(files: TestSuite[]) {
    console.log('Test run finished!')
    console.log(`Total files: ${files.length}`)
  }
  
  onTaskUpdate(task: TestCase) {
    if (task.result?.state === 'pass') {
      console.log(`✓ ${task.name}`)
    } else if (task.result?.state === 'fail') {
      console.log(`✗ ${task.name}`)
    }
  }
}

使用自定义报告器 #

typescript
// vitest.config.ts
import { defineConfig } from 'vitest'
import CustomReporter from './custom-reporter'

export default defineConfig({
  test: {
    reporters: [
      'default',
      new CustomReporter(),
    ],
  },
})

内置报告器 #

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

export default defineConfig({
  test: {
    reporters: [
      'default',    // 默认报告器
      'verbose',    // 详细报告
      'dot',        // 点状报告
      'json',       // JSON 报告
      'html',       // HTML 报告
      'junit',      // JUnit XML 报告
    ],
  },
})

调试测试 #

使用 VS Code 调试 #

json
// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Current Test File",
      "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/vitest",
      "runtimeArgs": [
        "run",
        "${file}"
      ],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}

使用 console.log 调试 #

typescript
import { test, expect } from 'vitest'

test('debug with console', () => {
  const data = { a: 1, b: 2 }
  
  console.log('data:', data)
  console.table(data)
  
  expect(data.a).toBe(1)
})

使用 debug 模块 #

typescript
// 安装 debug
// npm install -D debug

import { test, expect } from 'vitest'
import debug from 'debug'

const log = debug('myapp:test')

test('debug test', () => {
  log('Starting test...')
  
  const result = complexOperation()
  log('Result: %o', result)
  
  expect(result).toBeDefined()
})

使用 Node.js 调试器 #

bash
# 启动调试模式
node --inspect-brk ./node_modules/.bin/vitest run

# 然后在 Chrome DevTools 中连接
# chrome://inspect

测试过滤 #

按名称过滤 #

bash
# 运行匹配名称的测试
vitest run -t "user"
vitest run -t "should create"

按文件过滤 #

bash
# 运行特定文件
vitest run test/user.test.ts

# 运行匹配模式的文件
vitest run "test/**/*.test.ts"

按项目过滤 #

bash
# 运行特定项目
vitest run --project=core

按变更过滤 #

bash
# 只运行变更文件相关的测试
vitest run --changed

# 指定基准分支
vitest run --changed main

测试隔离 #

线程模式 #

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

export default defineConfig({
  test: {
    // 多线程运行(默认)
    threads: true,
    
    // 设置线程数
    maxThreads: 4,
    minThreads: 1,
    
    // 或单线程运行
    // threads: false,
  },
})

隔离模式 #

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

export default defineConfig({
  test: {
    // 每个测试文件在独立环境中运行
    isolate: true,
    
    // 每个测试文件单独进程
    // isolate: false 会共享环境
  },
})

测试钩子 #

全局钩子 #

typescript
// test/setup.ts
import { beforeAll, afterAll, beforeEach, afterEach } from 'vitest'

beforeAll(() => {
  console.log('All tests starting')
})

afterAll(() => {
  console.log('All tests finished')
})

beforeEach(() => {
  console.log('Each test starting')
})

afterEach(() => {
  console.log('Each test finished')
})

配置全局钩子 #

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

export default defineConfig({
  test: {
    setupFiles: ['./test/setup.ts'],
    globalSetup: ['./test/globalSetup.ts'],
  },
})

globalSetup 文件 #

typescript
// test/globalSetup.ts
export default function setup() {
  console.log('Global setup')
  
  // 返回 teardown 函数
  return function teardown() {
    console.log('Global teardown')
  }
}

快照管理 #

快照配置 #

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

export default defineConfig({
  test: {
    snapshotFormat: {
      escapeString: true,
      printBasicPrototype: true,
      indent: 2,
    },
    
    // 快照更新模式
    update: false, // 'all' | 'new' | 'none'
  },
})

快照操作 #

bash
# 更新所有快照
vitest -u

# 只更新失败的快照
vitest -u --run

# 更新新快照
vitest --update=new

测试环境变量 #

设置环境变量 #

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

export default defineConfig({
  test: {
    env: {
      NODE_ENV: 'test',
      API_URL: 'http://test-api.example.com',
    },
  },
})

在测试中使用 #

typescript
import { test, expect } from 'vitest'

test('environment variables', () => {
  expect(process.env.NODE_ENV).toBe('test')
  expect(process.env.API_URL).toBe('http://test-api.example.com')
})

测试依赖 #

指定依赖 #

typescript
import { test, expect } from 'vitest'

test('with dependencies', () => {
  // ...
}, {
  // 指定依赖文件
  depends: ['./setup.ts'],
})

测试并发控制 #

并发配置 #

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

export default defineConfig({
  test: {
    // 全局并发
    sequence: {
      concurrent: true,
    },
    
    // 或在测试中控制
    // test.concurrent()
  },
})

串行测试 #

typescript
import { test, describe } from 'vitest'

describe.serial('serial tests', () => {
  test('test 1', async () => {
    // 等待完成
  })
  
  test('test 2', async () => {
    // test 1 完成后执行
  })
})

最佳实践 #

1. 合理组织测试文件 #

text
project/
├── src/
│   ├── utils/
│   │   ├── sum.ts
│   │   └── sum.test.ts
│   └── components/
│       ├── Button.tsx
│       └── Button.test.tsx
├── test/
│   ├── setup.ts
│   ├── integration/
│   └── e2e/
└── vitest.config.ts

2. 使用 TypeScript 类型 #

typescript
// test/utils.ts
import { expect } from 'vitest'

export function expectType<T>(value: T): void {
  // 类型检查
}

// 使用
test('type safety', () => {
  const result = calculate()
  expectType<number>(result)
})

3. 配置合理的超时 #

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

export default defineConfig({
  test: {
    testTimeout: 10000,
    hookTimeout: 10000,
  },
})

4. 使用测试工具函数 #

typescript
// test/helpers.ts
import { vi } from 'vitest'

export function createMockResponse<T>(data: T): Response {
  return {
    ok: true,
    json: async () => data,
  } as Response
}

export function waitFor(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms))
}

5. 清理测试副作用 #

typescript
import { test, beforeEach, afterEach, vi } from 'vitest'

beforeEach(() => {
  vi.useFakeTimers()
})

afterEach(() => {
  vi.useRealTimers()
  vi.clearAllMocks()
})

test('clean test', () => {
  // 测试代码
})

高级特性速查表 #

特性 说明
浏览器模式 在真实浏览器中运行测试
工作区 管理多个测试配置
In-Source Testing 在源文件中编写测试
自定义报告器 创建自定义测试报告
调试 VS Code / Node.js 调试
测试过滤 按名称/文件/项目过滤
测试隔离 线程/进程隔离
全局钩子 全局 setup/teardown

总结 #

恭喜你完成了 Vitest 文档的学习!现在你已经掌握了:

  1. 基础概念:了解 Vitest 的特点和优势
  2. 安装配置:学会配置 Vitest 测试环境
  3. 编写测试:掌握测试结构和生命周期
  4. 断言方法:使用各种断言验证结果
  5. Mock 功能:模拟函数和模块
  6. 快照测试:测试 UI 组件和数据结构
  7. 异步测试:处理异步代码测试
  8. 代码覆盖率:测量测试覆盖程度
  9. 性能测试:进行基准测试对比
  10. 高级特性:浏览器模式、工作区等

继续实践,将测试融入日常开发流程,提高代码质量和开发效率!

最后更新:2026-03-28