使用 React Testing Library 的 15 个常见错误(上)

简介: 哈喽,大家好,我是海怪。 刚开始我在写项目的单测方案的时候,老板就让我能够写一些单测的规范。虽然表面上我非常自然地说:没问题,但是心里还是慌得不行:以前我自己写单测也没啥规范呀,直接开干就好了。 最近一直在看 Kent 的文章,刚好看到他写的这篇 《Common mistakes with React Testing Library》,里面列举了很多别人写单测时经常犯的一些错误 。正好可以作为单测规范的参考。所以,今天就把这篇文章也分享给大家~

image.png

前言


哈喽,大家好,我是海怪。


刚开始我在写项目的单测方案的时候,老板就让我能够写一些单测的规范。虽然表面上我非常自然地说:没问题,但是心里还是慌得不行:以前我自己写单测也没啥规范呀,直接开干就好了。


最近一直在看 Kent 的文章,刚好看到他写的这篇 《Common mistakes with React Testing Library》里面列举了很多别人写单测时经常犯的一些错误 。正好可以作为单测规范的参考。所以,今天就把这篇文章也分享给大家~


翻译中会尽量用更地道的语言,这也意味着会给原文加一层 Buf,想看原文的可点击 这里


正片开始


哈喽,大家好。以前的我(Kent)并不是很喜欢那个时候的测试环境,为此写了一个 React Testing Library。它是原来 DOM Testing Library 的一个扩展,随着不断更新迭代,现在 Testing Library 的实现也能支持当下所有流行的 JS 框架和工具来定位组件中的 DOM 了。


随时代发展,我们也对这个库的 API 做了很多修改,同时也发现社区中有很多不怎么优雅的使用方式。虽然我们已经很努力地在文档里写要怎么 “更好地” 使用我们提供的工具 API,但我还是在别的文章和博客中看到他们在用这些不优雅的使用方法。接下来,我就一一盘点这些方法,解释为什么它们不是很好,以及如何改进测试以避免这些陷阱。


注:下面是重要程度的说明。

  • 低:一般为我的主观想法,如果你觉得使用上没啥问题可以忽略它
  • 中:如果你不遵循,可能会出现 Bugs、低效的测试用例、还可能会做额外的工作
  • 高:一定要用我建议的方法。不然很有可能你会遇到大问题,而且测试用例并不怎么高效


没有使用 Testing Library 的 ESLint 插件


重要程度:中


如果你想避免这些常见的错误,那么官方的 ESLint 插件可以给你带来很多帮助:



注:如果你已经在用 create-react-library,那 eslint-plugin-testing-library 已经包含包在依赖中了

建议:最好把这两个 ESLint 插件都装上。


还在用 Wrapper 作为 render 返回值的变量名


重要程度:低


// ❌
const wrapper = render(<Example prop="1" />)
wrapper.rerender(<Example prop="2" />)
// ✅
const {rerender} = render(<Example prop="1" />)
rerender(<Example prop="2" />)


Wrapper 是以前 Enzyme 的过时用法,现在已经不需要它了。而且 render 的返回值里也并没有 Wraper 任何东西,它只是一些工具 API 的集合而已。所以,一般情况下可以不需要它了。


建议:直接使用从 render 返回值解构出来的东西,或者将返回值命名为 view


手动使用 cleanup



重要性:中


// ❌
import {render, screen, cleanup} from '@testing-library/react'
afterEach(cleanup)
// ✅
import {render, screen} from '@testing-library/react'


现在cleanup 都是自动调用的,所以你已经不再需要再考虑它了。详见这里


建议:别手动调 cleanup


不用 screen



重要程度:中


// ❌
const {getByRole} = render(<Example />)
const errorMessageNode = getByRole('alert')
// ✅
render(<Example />)
const errorMessageNode = screen.getByRole('alert')


screen是在 DOM Testing Library v6.11.0 引入的 (就就是说,你可以在 @testing-library/react@>=9 这些版本中使用它)。直接在 render 引入的时候一并引入就可以了:


import {render, screen} from '@testing-library/react'


使用 screen 的好处是:在添加/删除 DOM Query 时,不需要实时地解构 render

返回值来获取内容。输入 screen,你的编辑器就能自动补全它里面的 API 了。


除非一种情况:你在配置 container 或者 baseElement。不过,你应该避免使用它们(因为我实在想不出使用它们的现实场景,除非你是在处理一些历史遗留问题)。

你也可以直接调 screen.debug 而不是 debug


建议:用 screen 来做 Querying 和 Debugging


使用错误的断言 API



重要程度:高  


const button = screen.getByRole('button', {name: /disabled button/i})
// ❌
expect(button.disabled).toBe(true)
// error message:
//  expect(received).toBe(expected) // Object.is equality
//
//  Expected: true
//  Received: false
// ✅
expect(button).toBeDisabled()
// error message:
//   Received element is not disabled:
//     <button />


上面的 toBeDisabled 来自 jest-dom 这个库。强烈建议大家使用 jest-dom,因为你能获得更好的错误信息。


建议:用 @testing-library/jest-dom 这个库


将不必要的操作放在 act



重要程度:中


// ❌
act(() => {
  render(<Example />)
})
const input = screen.getByRole('textbox', {name: /choose a fruit/i})
act(() => {
  fireEvent.keyDown(input, {key: 'ArrowDown'})
})
// ✅
render(<Example />)
const input = screen.getByRole('textbox', {name: /choose a fruit/i})
fireEvent.keyDown(input, {key: 'ArrowDown'})


我经常看到不少人像上面那样把一些操作放在 act 里,因为他们一看到 "act" 的 Warning,就把操作放在 act 里面,以此去掉 Warning。但他们不知道的是 renderfireEvent 已经包裹在 act 里了!所以这样么其实没啥卵用。


大多数时间,如果你看到这些 act 的 Warning,不是要让你无脑地干掉它们,是在告诉

你:你的测试有问题了。可以看这里的视频来了解更多:Fix the "not wrapped in act(...)" warning


建议:去了解什么时候应该用 act,别把啥东西都往 act 里放


使用错误的 Query



重要程度:高


// ❌
// 假设你有这样的 DOM:
// <label>Username</label><input data-testid="username" />
screen.getByTestId('username')
// ✅
// 改成通过关联 label 以及设置 type 来访问 DOM
// <label for="username">Username</label><input id="username" type="text" />
screen.getByRole('textbox', {name: /username/i})


我们文档里一直有维护一个页面:“Which query should I use?”。你应该按这个页面中的顺序来使用 Query API。如果你的目标和我们的一样,都想通过测试来确保用户在使用时应用能够正常工作的话,那你就要尽量用更接近用户的使用方式来查询 DOM。我们提供的 Query 都能帮你做到这一点,但并非所有 Query API 都是一样的。


使用 container 来查询元素


作为 “使用错误的 Query” 的子集,我想聊一下直接用 container 来查询元素的问题:


// ❌
const {container} = render(<Example />)
const button = container.querySelector('.btn-primary')
expect(button).toHaveTextContent(/click me/i)
// ✅
render(<Example />)
screen.getByRole('button', {name: /click me/i})


实际上我们更希望用户能直接和 UI 进行交互,然而,如果你用 querySelector 这些来做查询的话,不仅我们不能模仿用户的 UI 交互行为,测试代码也会变得很难读,而且容易崩。这和下面这一节也有关系:


没有用文本来做查询


作为 “使用错误的 Query” 的子集,我想聊一下为什么我们更建议你用真实的文本来做查询(关于地区语言,应该用默认的地区语言文本),而不是用 Test ID 以及别的一些机制。


// ❌
screen.getByTestId('submit-button')
// ✅
screen.getByRole('button', {name: /submit/i})


如果不用真实的文本来查询,那你要做很多额外的工作,因为你要确保你的地区语言的翻译转换是正确的。这里肯定有多人会吐槽说:要是别人改了文本的内容,你的测试不就崩了么?我对此的反驳是,首先,如果有人将 “UserName” 更改为 “Email”,这是我绝对想知道的变更(因为我需要更改我的实现了)。而且,就算有人因为改了个名搞崩了测试,修复测试也用不了多长时间,马上就能修好了。


总的来说,修复的成本是很低的,而好处则是可以增加你对翻译正确性信心,而且写出来的测试也是容易阅读和修改的。


还是要声明一下,并不是所有人都同意我这个观点的,具体可以看下 Twitter 上的这个 Thread


多数情况下没有使用 *ByRole


作为 “使用错误的 Query” 的子集,我想来聊聊 *ByRole。在最近 RTL 的几个版本里,对 *ByRole 相关的 Query API 都做了很多的升级,这了是对组件渲染输出做查询操作最推荐的方法。下面是我比较喜欢它的一些功能。


name 选项可以让你通过元素的 "Accessible Name" 查询元素,这也是 Screen Reader 会对每个元素读取的内容。好处是:即使元素的文本内容被其它不同元素分割了,它还是能够以此做查询。比如:


// 假如现在我们有这样的 DOM:
// <button><span>Hello</span> <span>World</span></button>
screen.getByText(/hello world/i)
// ❌ 报错:
// Unable to find an element with the text: /hello world/i. This could be
// because the text is broken up by multiple elements. In this case, you can
// provide a function for your text matcher to make your matcher more flexible.
screen.getByRole('button', {name: /hello world/i})
// ✅ 成功!


人们不使用 *ByRole 做查询的原因之一是他们不熟悉在元素上的隐式 Role。,没关系,大家可以参考 MDN,MDN 上有写这些元素上的 Role List


另一个我喜欢这个 API 的功能是:如果不能通过指定好的 Role 找到元素,它不仅会像 get* 以及 find* API 一样把整个 DOM 树都打印出来,而且还会把当前能访问的 Role 都打印出来!


// 假设我们有这样的 DOM
// <button><span>Hello</span> <span>World</span></button>
screen.getByRole('blah')


上面会报这样的错误:


TestingLibraryElementError: Unable to find an accessible element with the role "blah"
Here are the accessible roles:
  button:
  Name "Hello World":
  <button />
  --------------------------------------------------
<body>
  <div>
    <button>
      <span>
        Hello
      </span>
      <span>
        World
      </span>
    </button>
  </div>
</body>

这里要注意的是,我们并没有为  设置 Role 而加上 role=button。因为这是隐式的 Role,下一节会详细说明。


建议:阅读并根据 “Which Query Should I Use" Guide” 里的推荐顺序来使用 Query

相关文章
|
7月前
|
资源调度 前端开发 JavaScript
React的测试:使用Jest和React Testing Library进行深入探索
【4月更文挑战第25天】本文探讨了使用Jest和React Testing Library进行React测试的方法。Jest是Facebook推出的JavaScript测试框架,适合React测试,提供全面的API和功能。React Testing Library侧重于组件行为,提倡按用户交互方式测试。安装这两个工具后,可通过编写测试用例(如模拟点击事件)来验证组件功能。运行Jest可执行测试并显示结果。此外,还介绍了高级测试技巧和模拟功能,强调了它们对于确保组件正确性、提升开发效率的重要性。
|
1月前
|
资源调度 前端开发 JavaScript
React 测试库 React Testing Library
【10月更文挑战第22天】本文介绍了 React Testing Library 的基本概念和使用方法,包括安装、基本用法、常见问题及解决方法。通过代码案例详细解释了如何测试 React 组件,帮助开发者提高应用质量和稳定性。
53 0
|
6月前
|
前端开发 JavaScript 测试技术
Jest与React Testing Library:前端测试的最佳实践
Jest和React Testing Library是React应用测试的核心工具。安装相关依赖后,在`jest.config.js`中配置Jest。测试时,编写描述性测试用例,使用`render`、`fireEvent`和`screen`来检查组件行为。Jest提供模拟功能,如模拟API调用。测试组件交互性时,模拟用户行为并验证状态变化。确保覆盖边缘情况,使用代码覆盖率报告评估测试完整性,并将测试集成到CI流程中。
106 1
|
前端开发
【React工作记录六十三】Http常见错误
【React工作记录六十三】Http常见错误
114 0
|
JavaScript 前端开发 测试技术
使用 React Testing Library 的 15 个常见错误(下)
哈喽,大家好,我是海怪。 刚开始我在写项目的单测方案的时候,老板就让我能够写一些单测的规范。虽然表面上我非常自然地说:没问题,但是心里还是慌得不行:以前我自己写单测也没啥规范呀,直接开干就好了。 最近一直在看 Kent 的文章,刚好看到他写的这篇 《Common mistakes with React Testing Library》,里面列举了很多别人写单测时经常犯的一些错误 。正好可以作为单测规范的参考。所以,今天就把这篇文章也分享给大家~
使用 React Testing Library 的 15 个常见错误(下)
|
7月前
|
设计模式 前端开发 数据可视化
【第4期】一文了解React UI 组件库
【第4期】一文了解React UI 组件库
382 0
|
7月前
|
存储 前端开发 JavaScript
【第34期】一文学会React组件传值
【第34期】一文学会React组件传值
80 0
|
7月前
|
前端开发
【第31期】一文学会用React Hooks组件编写组件
【第31期】一文学会用React Hooks组件编写组件
83 0
|
7月前
|
存储 前端开发 JavaScript
【第29期】一文学会用React类组件编写组件
【第29期】一文学会用React类组件编写组件
80 0
|
7月前
|
资源调度 前端开发 JavaScript
React 的antd-mobile 组件库,嵌套路由
React 的antd-mobile 组件库,嵌套路由
131 0