第一个测试:编写第一个React组件测试
好,咱们直接进入正题。今天要聊的是React测试里最基础、也最核心的一步——写第一个组件测试。说实话,我当年刚开始学测试的时候,总觉得这东西玄乎得很,不就是跑个代码看看对不对吗?后来踩了坑才明白,测试不是给机器看的,是给人看的。
理解render和screen
先说说两个最重要的工具:render和screen。这俩是React Testing Library里的核心API,说白了就是帮你把组件渲染出来,然后让你能像人一样去“看”它。
render函数的作用很简单——把React组件挂载到一个虚拟的DOM环境里。注意,这不是浏览器,是JSDom模拟出来的环境。我刚开始做项目时,以为render就是真的渲染到浏览器里了,结果调试了半天发现console.log打不出来,后来才明白,测试环境跟浏览器环境是两码事。
screen呢?它是个全局对象,提供了各种查询方法。我个人习惯用screen.getByText、screen.getByRole这些,因为它们更贴近用户视角。你想想看,用户不会去看什么data-testid,他们只关心页面上有没有显示“提交”两个字。
核心区别:
render:把组件放进虚拟DOMscreen:提供查询方法,帮你找到渲染后的元素
使用getByText查询
getByText是我用得最多的查询方法。它的逻辑很简单:在渲染后的DOM里,找到包含指定文本内容的元素。比如你有个按钮写着“登录”,那screen.getByText('登录')就能找到它。
这里有个坑,我曾经踩过:getByText默认是精确匹配。如果你写getByText('登录'),它不会匹配“登录系统”或者“登录按钮”。要模糊匹配的话,得用getByText(/登录/)这种正则写法。
小技巧:如果页面上有多个相同文本,getByText会报错。这时候可以用getAllByText,它会返回一个数组。
第一个测试长什么样?
咱们直接看代码。假设有个最简单的组件:
// Greeting.jsx
function Greeting({ name }) {
return <h1>你好,{name}!</h1>;
}
export default Greeting;
对应的测试文件:
// Greeting.test.jsx
import { render, screen } from '@testing-library/react';
import Greeting from './Greeting';
test('应该显示问候语', () => {
render(<Greeting name="小明" />);
const greetingElement = screen.getByText('你好,小明!');
expect(greetingElement).toBeInTheDocument();
});
这段代码做了三件事:
- 用
render把组件渲染出来 - 用
screen.getByText找到那个h1元素 - 用
expect断言它确实存在于文档中
嗯,就这么简单。我第一次写测试时,总觉得得搞点复杂的,结果发现最基础的反而最实用。
常见错误与避坑
| 错误写法 | 正确写法 | 说明 |
|---|---|---|
getByText('你好,小明') |
getByText('你好,小明!') |
标点符号必须完全一致 |
getByText('你好') |
getByText(/你好/) |
需要模糊匹配时用正则 |
忘记写expect |
加上expect(...).toBeInTheDocument() |
不写断言等于没测 |
注意:如果组件里没有你要找的文本,getByText会直接抛异常,测试就会失败。这其实是好事——它帮你尽早发现问题。
为什么推荐getByText?
我个人觉得,getByText最大的好处是:它逼着你从用户角度思考。用户不会去看什么className、id,他们只关心页面上有没有“提交”、“取消”、“确认删除”这些文字。你写测试的时候,其实就是在模拟用户的操作路径。
我曾经在一个项目里,看到有人用getByTestId到处加data-testid属性。结果后来重构时,组件结构变了,那些testid全废了,测试全崩。而用getByText的测试,只要文案没变,基本不受影响。
总结一下
写第一个测试,记住三句话:
render把组件放进去screen.getByText把元素找出来expect断言它存在
就这么简单。别想复杂了,测试的本质就是“我期望它这样,它确实这样”。
下一章咱们会聊更复杂的查询,比如getByRole和getByPlaceholderText,到时候再深入。