第一个测试:编写第一个React组件测试

好,咱们直接进入正题。今天要聊的是React测试里最基础、也最核心的一步——写第一个组件测试。说实话,我当年刚开始学测试的时候,总觉得这东西玄乎得很,不就是跑个代码看看对不对吗?后来踩了坑才明白,测试不是给机器看的,是给人看的。

理解render和screen

先说说两个最重要的工具:renderscreen。这俩是React Testing Library里的核心API,说白了就是帮你把组件渲染出来,然后让你能像人一样去“看”它。

render函数的作用很简单——把React组件挂载到一个虚拟的DOM环境里。注意,这不是浏览器,是JSDom模拟出来的环境。我刚开始做项目时,以为render就是真的渲染到浏览器里了,结果调试了半天发现console.log打不出来,后来才明白,测试环境跟浏览器环境是两码事。

screen呢?它是个全局对象,提供了各种查询方法。我个人习惯用screen.getByTextscreen.getByRole这些,因为它们更贴近用户视角。你想想看,用户不会去看什么data-testid,他们只关心页面上有没有显示“提交”两个字。

核心区别:

  • render:把组件放进虚拟DOM
  • screen:提供查询方法,帮你找到渲染后的元素

使用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();
});

这段代码做了三件事:

  1. render把组件渲染出来
  2. screen.getByText找到那个h1元素
  3. expect断言它确实存在于文档中

嗯,就这么简单。我第一次写测试时,总觉得得搞点复杂的,结果发现最基础的反而最实用。

常见错误与避坑

错误写法 正确写法 说明
getByText('你好,小明') getByText('你好,小明!') 标点符号必须完全一致
getByText('你好') getByText(/你好/) 需要模糊匹配时用正则
忘记写expect 加上expect(...).toBeInTheDocument() 不写断言等于没测

注意:如果组件里没有你要找的文本,getByText会直接抛异常,测试就会失败。这其实是好事——它帮你尽早发现问题。

为什么推荐getByText?

我个人觉得,getByText最大的好处是:它逼着你从用户角度思考。用户不会去看什么className、id,他们只关心页面上有没有“提交”、“取消”、“确认删除”这些文字。你写测试的时候,其实就是在模拟用户的操作路径。

我曾经在一个项目里,看到有人用getByTestId到处加data-testid属性。结果后来重构时,组件结构变了,那些testid全废了,测试全崩。而用getByText的测试,只要文案没变,基本不受影响。

总结一下

写第一个测试,记住三句话:

  • render把组件放进去
  • screen.getByText把元素找出来
  • expect断言它存在

就这么简单。别想复杂了,测试的本质就是“我期望它这样,它确实这样”。

下一章咱们会聊更复杂的查询,比如getByRolegetByPlaceholderText,到时候再深入。