ToolkitX
知识库工具箱

测试

React Testing Library 组件测试

20min·进阶

01. 为什么需要测试

写代码就像搭积木,你搭好了积木要验收对不对。测试就是验收环节,帮你自动检查代码有没有按预期运行。React 组件测试跟前端自动点击页面很像,只不过是用代码来模拟用户操作。没测试的代码就像没有说明书的电器,出了毛病你都不知道从哪里修起。
javascript
import { render, screen } from '@testing-library/react'
import App from './App'

test('renders hello', () => {
  render(<App />)
  const el = screen.getByText(/hello/i)
  expect(el).toBeInTheDocument()
})
写测试不要追求覆盖率数字,要追求有价值的用例。

02. 安装和配置测试环境

Create React App 自带 Jest 和 Testing Library,不需要额外装。如果是手动搭的 Vite 项目,需要自己装 vitest 或 jest。关键是要配好 jsdom 环境,因为测试是在 Node 里跑的,没有真实的浏览器 DOM,jsdom 帮我们模拟了一个浏览器环境。
bash
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
# 或者用 jest
npm install -D jest @testing-library/react @testing-library/jest-dom
vitest 比 jest 快很多,新项目推荐用 vitest。

03. 查询 DOM 元素的各种姿势

Testing Library 提供了一堆查询方法,按优先级排列:getByRole 最推荐,因为最贴近无障碍体验。然后是 getByLabelText、getByPlaceholderText、getByText、getByTestId。getBy 找不到元素会直接抛异常,queryBy 找不到返回 null,findBy 是异步的等待元素出现。
javascript
const btn = screen.getByRole('button', { name: /提交/i })
const input = screen.getByLabelText(/用户名/i)
const title = screen.getByText(/欢迎回来/)
const list = await screen.findByRole('list')
尽量少用 getByTestId,这个东西跟实现细节耦合太紧。

04. 模拟用户交互

用 fireEvent 或 userEvent 来模拟用户操作。userEvent 比 fireEvent 更接近真实用户行为,比如 userEvent.type 会触发 keyDown、keyPress、keyUp 等一系列事件,而 fireEvent 只触发你指定的那一个。推荐优先用 userEvent。
javascript
import userEvent from '@testing-library/user-event'

test('user can type and submit', async () => {
  const user = userEvent.setup()
  render(<LoginForm />)
  await user.type(screen.getByLabelText(/邮箱/), '[email protected]')
  await user.click(screen.getByRole('button', { name: /登录/ }))
  await waitFor(() => expect(mockLogin).toHaveBeenCalled())
})

05. Mock 和异步测试

Mock 就是造假数据糊弄测试。比如你组件里调了 fetch,在测试里不可能真的发请求,就得用 jest.fn() 伪造一个假的 fetch 函数。异步测试要注意用 waitFor 或 findBy 等待断言,不然测试跑完了 Promise 还没 resolve 就炸了。
javascript
global.fetch = jest.fn(() =>
  Promise.resolve({ json: () => Promise.resolve({ name: '小明' }) })
)

test('loads user name', async () => {
  render(<UserCard id={1} />)
  await waitFor(() => {
    expect(screen.getByText('小明')).toBeInTheDocument()
  })
})
Mock 用多了会让测试失去意义,尽量少 mock 核心业务逻辑。

知识测验

1/5正确 0

React Testing Library 中最推荐的查询方法是什么?