前端组件测试实战:Vitest 与 Testing Library

前端组件怎么测?Vitest 写单元测试,Testing Library 渲染组件模拟用户操作,覆盖率报告看哪些代码没测到。本文从环境搭建到测试策略,讲清前端组件测试的完整流程。

前端测试配图:蓝色渐变背景上悬浮岛平台带盾牌图标与速度仪表盘,扁平科技插画
Vitest + Testing Library:让前端代码有测试保障

一、为什么前端需要测试

前端改一个按钮样式,结果整个页面崩了?没有测试,每次改代码都是手动点一遍页面。自动化测试让你改完代码跑一遍测试就知道有没有破坏功能。

测试类型测什么工具
单元测试函数、工具类Vitest / Jest
组件测试UI 组件渲染与交互Testing Library
E2E 测试完整用户流程Playwright / Cypress

二、搭建 Vitest 环境

# 安装
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom

# vitest.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: './src/setupTests.js',
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html'],
    },
  },
})

# package.json 加脚本
# "test": "vitest",
# "test:coverage": "vitest --coverage"
Vitest vs Jest:Vitest 是 Vite 原生的测试框架,速度更快、配置更简单、和 Vite 项目无缝集成。新项目推荐用 Vitest,老项目用 Jest 也没问题。

三、第一个组件测试

// Button.jsx
export function Button({ label, onClick }) {
  return <button onClick={onClick}>{label}</button>
}

// Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from './Button'

test('按钮显示正确的文字', () => {
  render(<Button label="提交" />)
  expect(screen.getByText('提交')).toBeInTheDocument()
})

test('点击按钮触发回调', () => {
  const handleClick = vi.fn()
  render(<Button label="提交" onClick={handleClick} />)
  fireEvent.click(screen.getByText('提交'))
  expect(handleClick).toHaveBeenCalledTimes(1)
})

四、异步组件测试

// UserProfile.jsx
import { useState, useEffect } from 'react'

export function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        setUser(data)
        setLoading(false)
      })
  }, [userId])

  if (loading) return <div>加载中...</div>
  return <div>{user.name}</div>
}

// UserProfile.test.jsx
import { render, screen } from '@testing-library/react'
import { UserProfile } from './UserProfile'

// Mock fetch
global.fetch = vi.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({ name: '小明' }),
  })
)

test('加载并显示用户名', async () => {
  render(<UserProfile userId="1" />)
  // 先显示加载中
  expect(screen.getByText('加载中...')).toBeInTheDocument()
  // 等待异步完成
  expect(await screen.findByText('小明')).toBeInTheDocument()
})

五、测试覆盖率

# 运行覆盖率报告
npm run test:coverage

# 输出示例:
# File           % Stmts   % Branch   % Funcs   % Lines
# All files       85%       70%       90%       85%
#  Button.jsx     100%      100%      100%      100%
#  UserProfile    75%       50%       80%       75%

覆盖率不是越高越好,80% 左右是合理目标。重点测核心业务逻辑和边界情况,不要为了覆盖率写无意义的测试。

六、测试最佳实践

按用户行为写测试:不要测试组件内部状态,测试用户看到什么、点什么。

Mock 外部依赖:API 请求、定时器、路由都 mock 掉,测试只关注组件本身。

一个测试只测一件事:不要在一个 test 里写多个断言场景,保持测试清晰。

用 getBy 优先:Testing Library 的查询优先级:getByRole > getByLabelText > getByText > getByTestId。

七、总结

前端测试的核心是 Vitest + Testing Library 的组合。Vitest 跑测试,Testing Library 渲染组件模拟用户操作。覆盖率看核心逻辑覆盖,不追求 100%。记住:测试是为了让你改代码时心里有底,不是为了写测试而写测试。