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

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

image.png


错误地添加可访问属性:aria-role



重要程度:高


// ❌
render(<button role="button">Click me</button>)
// ✅
render(<button>Click me</button>)


像上面那样随意添加/修改可访问属性(Accessibility Attributes)不仅没有必要,而且还会把 Screen Reader 和用户搞懵。只有当无法满足当前的 HTML 语义时(比如你写了一个非原生的 UI 组件,同时也要让它 像 AutoComplete 一样可访问),你才应该使用可访问属性。假如这就是你现在要开发的东西,那可以用现有的第三库根据 WAI-ARIA 实践来实现可访问性。它们一般会有一些 很好的样例来参考


注意:如果要让 input 可以通过 role 来访问,你需要指定对应的 type 属性值!

建议:避免错误地添加不必要的或不正确的可访问属性


没有使用 @testing-library/user-event



重要程度:高


// ❌
fireEvent.change(input, {target: {value: 'hello world'}})
// ✅
userEvent.type(input, 'hello world')


@testing-library/user-event 是在 fireEvent 基础上实现的,但它提供了一些更接近用户交互的方法。上面这个例子中,fireEvent.change 其实只触发了 Input 的一个 Change 事件。但是 type 则可以对每个字符都会触发 keyDownkeyPresskeyUp 一系列事件。这能更接近用户的真实交互场景。好处是可以很好地和你当前那些没有监听 Change 事件的库一起使用。


我们现在还在进行 @testing-library/user-event 这个库的开发,来保证它能像它承诺的那样:能够触发用户在执行特定操作时会触发的所有相同事件。不过,现在它还没完全做到这一点,这也是为什么它还没有合入 @testing-library/dom (可能在未来的某个时候会合入)。 但是,我对它有足够的信心,建议你多关注和使用它,而不是 fireEvent


建议:尽可能地使用 @testing-library/user-event,而不是 fireEvent


没有用 query* 来断言元素不存在



重要程度:高


// ❌
expect(screen.queryByRole('alert')).toBeInTheDocument()
// ✅
expect(screen.getByRole('alert')).toBeInTheDocument()
expect(screen.queryByRole('alert')).not.toBeInTheDocument()


把暴露 query* 相关的 API 出来的唯一原因是:可以在找不到元素的情况下不会抛出异常(返回  null)。唯一的好处是可以用来判断这个元素是否没有被渲染到页面上。这是很重要的,因为类似 get*find* 相关的 API 在找不到元素时都会自动抛出异常 —— 这样你就可以看到渲染的内容以及为什么找不到元素的原因。然而,query* 只会返回 null,所以 toBeInTheDocument 在这里最好的用法就是:判断 null 不在 Document 上。


建议:query* API 只用于断言当前元素不能被找到


waitFor 等待 find* 的查询结果



重要程度:高


// ❌
const submitButton = await waitFor(() =>
  screen.getByRole('button', {name: /submit/i}),
)
// ✅
const submitButton = await screen.findByRole('button', {name: /submit/i})


上面两段代码几乎是等价的(find* 其实也是在内部用了 waitFor),但是第二种使用方法更清晰,而且抛出的错误信息会更友好。


建议:当查询那些不能立马能访问到的元素时,使用 find*


waitFor 传空 callback



重要程度:高


// ❌
await waitFor(() => {})
expect(window.fetch).toHaveBeenCalledWith('foo')
expect(window.fetch).toHaveBeenCalledTimes(1)
// ✅
await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('foo'))
expect(window.fetch).toHaveBeenCalledTimes(1)


waitFor 的目的是可以让你等一些指定的事情发生。如果传了空的 callback,可能它在今天还能 Work,因为你只是想在 Event Loop 等一个 Tick 就好了。但这样你也会留下一个脆弱的测试用例,一旦改了某些异步逻辑它很可能就崩了。


建议:在 waitFor 里等待指定的断言,不要传空 callback


一个 waitFor callback 里有多个断言



重要程度:低


// ❌
await waitFor(() => {
  expect(window.fetch).toHaveBeenCalledWith('foo')
  expect(window.fetch).toHaveBeenCalledTimes(1)
})
// ✅
await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('foo'))
expect(window.fetch).toHaveBeenCalledTimes(1)


在上面的例子中,如果 window.fetch 调用了两次,那么 waitFor 就会失败,但是我们就得等到超时了才能看到具体报错。而如果 waitFor 里只有一个断言,我们则可以等待 UI 渲染到断言的同时,也可以在其中一个断言失败时更快地获得报错信息。


建议:waitFor 的 callback 里只放一个断言


waitFor 中使用副作用



重要程度:高


// ❌
await waitFor(() => {
  fireEvent.keyDown(input, {key: 'ArrowDown'})
  expect(screen.getAllByRole('listitem')).toHaveLength(3)
})
// ✅
fireEvent.keyDown(input, {key: 'ArrowDown'})
await waitFor(() => {
  expect(screen.getAllByRole('listitem')).toHaveLength(3)
})


waitFor 适用的情况是:在执行的操作和断言之间存在不确定的时间量。因此,callback 可在不确定的时间和频率(在间隔以及 DOM 变化时调用)被调用(或者检查错误)。所以这也意味着你的副作用可能会被多次调用!

同时,这也意味着你不能在 waitFor 里面使用快照断言(SnapShot Assertion)。如果你想要用快照断言,首先要等待某些断言走完了,然后再拍快照。


建议:把副作用放在 waitFor 回调的外面,回调里只能有断言


get* 来做断言



重要程度:低


// ❌
screen.getByRole('alert', {name: /error/i})
// ✅
expect(screen.getByRole('alert', {name: /error/i})).toBeInTheDocument()


虽然这不是什么大问题,但我还是想说下我的观点。如果 get* API 找不到元素,它就

会抛出异常,打印整个 DOM 树结构(语法高亮),在 Debug 的时候很有用。也因为这点,断言是永远不可能失败的(因为如果找不到元素,查询在断言之前抛出异常)。

因为这个原因,很多人直接不做断言了。这其实也还好,但是我个人通常来说,会把断言留着,这样可以让后面做重构、修改的人知道:这里不是个查询操作,而是个断言操作。


建议:如果你想断言某个东西是否存在,那么就做显式的断言操作


总结



作为测试库工具系列的维护者,我们尽最大努力使 API 能够引导人们尽可能有效地使用,一些不足之处,我们会尝试正确地记录下来,即使这会非常地困难(尤其是 API 改动/升级等)。希望这篇文章会帮到你,我们只是想你更有信心地交付你的代码。

Good Luck!



好了,这篇外文就给大家带到这里了。翻译这篇文章还是花不少时间的,同时也学到了很多 RTL 这个库的一些思想,希望大家也能吸收里面一些测试思路。

相关文章
|
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 组件,帮助开发者提高应用质量和稳定性。
47 0
|
6月前
|
前端开发 JavaScript 测试技术
Jest与React Testing Library:前端测试的最佳实践
Jest和React Testing Library是React应用测试的核心工具。安装相关依赖后,在`jest.config.js`中配置Jest。测试时,编写描述性测试用例,使用`render`、`fireEvent`和`screen`来检查组件行为。Jest提供模拟功能,如模拟API调用。测试组件交互性时,模拟用户行为并验证状态变化。确保覆盖边缘情况,使用代码覆盖率报告评估测试完整性,并将测试集成到CI流程中。
96 1
|
前端开发
【React工作记录六十三】Http常见错误
【React工作记录六十三】Http常见错误
110 0
|
JavaScript 前端开发 测试技术
使用 React Testing Library 的 15 个常见错误(上)
哈喽,大家好,我是海怪。 刚开始我在写项目的单测方案的时候,老板就让我能够写一些单测的规范。虽然表面上我非常自然地说:没问题,但是心里还是慌得不行:以前我自己写单测也没啥规范呀,直接开干就好了。 最近一直在看 Kent 的文章,刚好看到他写的这篇 《Common mistakes with React Testing Library》,里面列举了很多别人写单测时经常犯的一些错误 。正好可以作为单测规范的参考。所以,今天就把这篇文章也分享给大家~
使用 React Testing Library 的 15 个常见错误(上)
|
7月前
|
设计模式 前端开发 数据可视化
【第4期】一文了解React UI 组件库
【第4期】一文了解React UI 组件库
373 0
|
7月前
|
存储 前端开发 JavaScript
【第34期】一文学会React组件传值
【第34期】一文学会React组件传值
79 0
|
7月前
|
前端开发
【第31期】一文学会用React Hooks组件编写组件
【第31期】一文学会用React Hooks组件编写组件
82 0
|
7月前
|
存储 前端开发 JavaScript
【第29期】一文学会用React类组件编写组件
【第29期】一文学会用React类组件编写组件
78 0
|
7月前
|
资源调度 前端开发 JavaScript
React 的antd-mobile 组件库,嵌套路由
React 的antd-mobile 组件库,嵌套路由
127 0