前端测试金字塔:单元、组件、E2E

前端测试分三层,像金字塔:底层是单元测试(最多、最快、测函数和工具)、中层是组件测试(测单个组件的渲染和交互)、顶层是 E2E 测试(最少、最慢、模拟真实用户操作整个流程)。

为什么叫金字塔?因为越往下应该越多:单元测试应该占 70%,组件测试占 20%,E2E 只占 10%。E2E 写起来慢、跑起来慢、还容易因为界面变化而挂掉——所以它只覆盖最关键的业务流程(登录、下单、支付),不覆盖所有细节。

不要一上来就写 E2E。先用单元测试把工具函数和业务逻辑覆盖住,再用组件测试测交互,最后挑 3-5 条核心路径写 E2E。
本文核心知识点
  • VitestVite 原生测试框架,零配置、极速、API 兼容 Jest。
  • Testing Library渲染组件,模拟用户交互,断言 DOM 状态。
  • Playwright浏览器自动化,写 E2E 测试像写操作脚本。
  • Mock拦截网络请求和模块依赖,让测试只测当前单元。
前端自动化测试示意:蓝色渐变背景上机械臂点击浏览器窗口,周围漂浮测试报告面板与勾选徽章
自动化测试就是机械臂:它替你一遍一遍重复点击、输入、验证,不会累也不会漏。

Vitest:前端单元测试的新标准

Jest 曾经是前端测试的唯一选择,但 Vitest 出来后情况变了。Vitest 和 Vite 共用同一套配置,启动速度极快,API 和 Jest 几乎一样——迁移成本很低。现在新项目用 Vitest 是主流。

npm install -D vitest

# 写一个测试文件 sum.test.js
import { add } from "./sum"
import { expect, test } from "vitest"

test("1 + 2 = 3", () => {
  expect(add(1, 2)).toBe(3)
})

test("负数相加", () => {
  expect(add(-1, -2)).toBe(-3)
})

运行 npx vitest,它会自动找到所有 *.test.js 文件并执行。和 Jest 一样,expect 提供各种断言:toBe、toEqual、toContain、toBeGreaterThan。

断言用途
toBe(x)严格相等(===)
toEqual(obj)深度比较对象
toContain(x)数组/字符串包含
toThrow()函数抛异常
toBeTruthy()值为真

Vitest 的 watch 模式很好用:改代码后自动重跑相关测试,开发时实时看到测试状态。

组件测试:Testing Library

组件测试比单元测试高一档:你把一个 React/Vue 组件渲染到虚拟 DOM 里,模拟用户点击、输入,然后断言页面显示的内容对不对。

import { render, screen, fireEvent } from "@testing-library/react"
import Counter from "./Counter"

test("点击按钮数字加一", () => {
  render(<Counter />)

  // 找到按钮,模拟点击
  const btn = screen.getByText("+1")
  fireEvent.click(btn)

  // 断言显示的数字变了
  expect(screen.getByText("1")).toBeInTheDocument()
})

Testing Library 的理念是像用户一样找元素——通过文本、角色、标签找,而不是通过 CSS 类名。这样测试和界面的视觉设计解耦:你改按钮颜色、调间距,测试不会挂;你改功能行为,测试会挂。

常用查询方式:getByText(按文字找)、getByRole(按 ARIA 角色找)、getByLabelText(按表单标签找)。这些都是用户视角的定位方式。

Playwright:端到端测试实战

E2E 测试是真正打开浏览器、访问你的网站、模拟用户操作。Playwright 是目前最好的 E2E 工具——它支持 Chromium、Firefox、WebKit 三个浏览器,自动等待、截图、录屏都内置。

npm init playwright@latest

// tests/login.spec.ts
import { test, expect } from "@playwright/test";

test("用户可以登录", async ({ page }) => {
  await page.goto("https://localhost:5173/login");

  // 填写表单
  await page.getByLabel("邮箱").fill("test@example.com");
  await page.getByLabel("密码").fill("123456");

  // 点击登录
  await page.getByRole("button", { name: "登录" }).click();

  // 断言跳转到首页
  await expect(page).toHaveURL(/\/home/);
  await expect(page.getByText("欢迎回来")).toBeVisible();
});

运行 npx playwright test,它会打开浏览器,自动走完整个流程。任何一步失败,它自动截图、保留日志、甚至录视频——调试极其方便。

Playwright 的超能力:自动等待。你写 click(),它会等元素出现、可点击、不被遮挡才点——不需要手动写 waitFor。这比老工具 Selenium 省心太多。

Mock 网络请求与模块

组件和单元测试不应该真的发 HTTP 请求——那样测试会慢、不稳定、还依赖后端可用。Vitest 提供了 mock 机制:把 fetch/axios 替换成假的,预设返回值。

import { vi, expect, test } from "vitest"

// mock 掉 fetch
vi.stubGlobal("fetch", vi.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({ name: "测试用户" })
  })
))

test("加载用户信息", async () => {
  const user = await fetchUser()
  expect(user.name).toBe("测试用户")
  expect(fetch).toHaveBeenCalledWith("/api/user")
})

这样测试只验证"我们的代码是否正确调用了接口、正确处理了返回值",不依赖真实后端。测试跑得快、结果稳定。

Playwright 也支持拦截网络请求——在 E2E 测试里 mock 后端接口,让前端测试不依赖后端服务是否可用。page.route("**/api/**", handler) 可以拦截所有 API 请求返回假数据。