从 Enzyme 迁移
此页面适用于有 Enzyme 使用经验并试图了解如何迁移到 React 测试库的开发人员。它不会详细介绍如何迁移所有类型的测试,但它确实提供了一些有用的信息,供那些将 Enzyme 与 React 测试库进行比较的人。
什么是 React 测试库?
React 测试库是名为 测试库 的开源项目的一部分。测试库项目中还有其他几个有用的工具和库,可用于编写更简洁、更有用的测试。除了 React 测试库,以下是该项目中的一些其他库,可以帮助您一路走来
@testing-library/jest-dom:
jest-dom
提供了一组自定义的 Jest 匹配器,您可以使用它们来扩展 Jest。这些使您的测试更具声明性,更易于阅读和维护。@testing-library/user-event:
user-event
尝试模拟用户与页面上的元素交互时发生的浏览器中的真实事件。例如,userEvent.click(checkbox)
将更改复选框的状态。
为什么要使用 React 测试库?
Enzyme 是一个强大的测试库,它的贡献者为 JavaScript 社区做了很多贡献。事实上,许多 React 测试库维护者在开发和使用 React 测试库之前,就使用过 Enzyme 并为其做出了贡献。因此,我们要感谢 Enzyme 的贡献者!
React 测试库的主要目的是通过以用户使用组件的方式测试组件来提高对测试的信心。用户不关心幕后发生了什么,他们只看到并与输出进行交互。您将通过根据组件输出编写测试来获得更大的信心,而不是访问组件的内部 API 或评估其 state
。
React 测试库旨在解决许多开发人员在使用 Enzyme 编写测试时面临的问题,Enzyme 允许(并鼓励)开发人员测试 实现细节。进行此操作的测试最终会阻止您修改和重构组件,而无需更改其测试。因此,测试会降低开发速度和生产力。每次小改动都可能需要重写测试的一部分,即使更改不会影响组件的输出。
用 React 测试库重写您的测试是值得的,因为您将用减慢您速度的测试来换取从长远来看会让您更有信心并提高生产力的测试。
如何从 Enzyme 迁移到 React 测试库?
为了确保迁移成功,我们建议您以增量方式进行,方法是在同一个应用程序中并排运行两个测试库,将您的 Enzyme 测试逐个移植到 React 测试库。这样一来,即使是大型且复杂的应用程序也可以迁移,而不会中断其他业务,因为这项工作可以协作完成并分散在一段时间内。
安装 React 测试库
首先,安装 React 测试库和 jest-dom
辅助库(您可以查看 此页面 以获取完整的安装和设置指南)。
- npm
- Yarn
npm install --save-dev @testing-library/react @testing-library/jest-dom
yarn add --dev @testing-library/react @testing-library/jest-dom
将 React 测试库导入到您的测试中
如果您使用的是 Jest(您可以使用其他测试框架),那么您只需要将以下模块导入到您的测试文件中即可
// import React so you can use JSX (React.createElement) in your test
import React from 'react'
/**
* render: lets us render the component as React would
* screen: a utility for finding elements the same way the user does
*/
import {render, screen} from '@testing-library/react'
测试结构可以与您使用 Enzyme 编写的方式相同
test('test title', () => {
// Your tests come here...
})
注意:您也可以将
describe
和it
块与 React 测试库一起使用。React 测试库不会取代 Jest,只是取代了 Enzyme。我们推荐test
,因为它有助于实现以下目的:在测试时避免嵌套。
基本 Enzyme 到 React 测试库迁移示例
要记住的一点是,Enzyme 功能与 React 测试库功能之间没有一一对应的映射。许多 Enzyme 功能会导致效率低下的测试,因此您在 Enzyme 中习惯的一些功能需要放弃(不再需要 wrapper
变量或 wrapper.update()
调用等)。
React 测试库提供了一些有用的查询,可以让您访问组件的元素及其属性。我们将展示一些典型的 Enzyme 测试以及使用 React 测试库的替代方案。
假设我们有一个 Welcome
组件,它显示一条欢迎消息。我们将看看 Enzyme 和 React 测试库的测试,以了解如何测试此组件
React 组件
以下组件从 props
中获取 name
,并在 h1
元素中显示欢迎消息。它还有一个文本输入,用户可以将其更改为不同的名称,模板会相应更新。查看 CodeSandbox 上的实时版本。
const Welcome = props => {
const [values, setValues] = useState({
firstName: props.firstName,
lastName: props.lastName,
})
const handleChange = event => {
setValues({...values, [event.target.name]: event.target.value})
}
return (
<div>
<h1>
Welcome, {values.firstName} {values.lastName}
</h1>
<form name="userName">
<label>
First Name
<input
value={values.firstName}
name="firstName"
onChange={handleChange}
/>
</label>
<label>
Last Name
<input
value={values.lastName}
name="lastName"
onChange={handleChange}
/>
</label>
</form>
</div>
)
}
export default Welcome
测试 1:渲染组件,并检查 h1
值是否正确
Enzyme 测试
test('has correct welcome text', () => {
const wrapper = shallow(<Welcome firstName="John" lastName="Doe" />)
expect(wrapper.find('h1').text()).toEqual('Welcome, John Doe')
})
React 测试库
test('has correct welcome text', () => {
render(<Welcome firstName="John" lastName="Doe" />)
expect(screen.getByRole('heading')).toHaveTextContent('Welcome, John Doe')
})
如您所见,测试非常相似。Enzyme 的 shallow
渲染器不会渲染子组件,因此 React 测试库的 render
方法更类似于 Enzyme 的 mount
方法。
在 React 测试库中,您不需要将 render
结果分配给变量(例如 wrapper
)。您可以通过在 screen
对象上调用函数来访问渲染的输出。另一件需要注意的是,React 测试库在每个测试后会自动清理环境,因此您不需要在 afterEach
或 beforeEach
函数中调用 cleanup
。
您可能注意到的另一个是 getByRole
,它以 'heading'
作为参数。'heading'
是 h1
元素的可访问角色。您可以在 查询文档页面 上了解有关它们的更多信息。人们很快就会学会喜欢测试库的一件事是它如何鼓励您编写更具可访问性的应用程序(因为如果它不可访问,那么测试起来就更困难)。
测试 2:输入文本必须具有正确的 value
在上面的组件中,输入值的初始化使用 props.firstName
和 props.lastName
值。我们需要检查 value 是否正确。
Enzyme
test('has correct input value', () => {
const wrapper = shallow(<Welcome firstName="John" lastName="Doe" />)
expect(wrapper.find('input[name="firstName"]').value).toEqual('John')
expect(wrapper.find('input[name="lastName"]').value).toEqual('Doe')
})
React 测试库
test('has correct input value', () => {
render(<Welcome firstName="John" lastName="Doe" />)
expect(screen.getByRole('form')).toHaveFormValues({
firstName: 'John',
lastName: 'Doe',
})
})
太棒了!它非常简单实用,并且测试足够清晰,我们无需过多讨论。您可能注意到的一个细节是,<form>
具有 role="form"
属性,但这到底是什么?
role
是可访问性相关属性之一,建议使用它来为残疾人改进您的 Web 应用程序。一些元素具有默认的 role
值,您不需要为它们设置,但其他一些元素(如 <div>
)没有默认的 role
值。您可以使用不同的方法来访问 <div>
元素,但我们建议尝试通过其隐式 role
来访问元素,以确保您的组件能够被残疾人和使用屏幕阅读器的人访问。这部分 查询文档可能有助于您更好地理解这些概念。
<form>
元素必须具有name
属性才能具有隐式role
为'form'
(如规范要求)。
React 测试库旨在以用户使用它们的方式测试组件。用户通过其角色来查看按钮、标题、表单和其他元素,而不是通过其 id
、class
或元素标签名称来查看。因此,当您使用 React 测试库时,应避免使用 document.querySelector
API 来访问 DOM。(您可以在测试中使用它,但出于本段中说明的原因,不建议这样做。)
React 测试库公开了一些方便的查询 API,可以帮助您高效地访问组件元素。您可以查看 此处提供的可用查询列表。如果您不确定在特定情况下应该使用哪个查询,我们有一个很棒的页面解释了 使用哪个查询,所以请查看!
如果您仍然对使用哪个 React 测试库查询存在疑问,请查看 testing-playground.com 和配套的 Chrome 扩展程序Testing Playground,其目的是使开发人员能够在编写测试时找到最佳查询。它还可以帮助您找到选择元素的最佳查询。它允许您检查 Chrome 开发者工具中的元素层次结构,并为您提供有关如何选择它们的建议,同时鼓励良好的测试实践。
使用 act() 和 wrapper.update()
在 Enzyme 中测试异步代码时,通常需要调用 `act()` 来正确运行测试。当使用 React Testing Library 时,大多数情况下不需要显式调用 `act()`,因为它默认情况下会将 API 调用包装在 `act()` 中。
`update()` 将 Enzyme 组件树快照与 React 组件树同步,因此您可能会在 Enzyme 测试中看到 `wrapper.update()`。React Testing Library 没有(也不需要)类似的方法,这对您来说是件好事,因为您需要处理的事情更少了!
模拟用户事件
使用 React Testing Library 模拟用户事件有两种方法。一种方法是使用 user-event
库,另一种方法是使用 fireEvent
,它包含在 React Testing Library 中。user-event
实际上是建立在 fireEvent
之上的(它只是在给定元素上调用 dispatchEvent
)。通常建议使用 user-event
,因为它确保所有事件都以正确的顺序触发,以进行典型的用户交互。这有助于确保您的测试模拟软件的实际使用方式。
要使用 `@testing-library/user-event` 模块,首先安装它
- npm
- Yarn
npm install --save-dev @testing-library/user-event @testing-library/dom
yarn add --dev @testing-library/user-event @testing-library/dom
现在您可以将其导入您的测试中
import userEvent from '@testing-library/user-event'
为了演示如何使用 `user-event` 库,假设我们有一个 `Checkbox` 组件,它显示一个复选框输入和一个关联的标签。我们想模拟用户单击复选框的事件
import React from 'react'
const Checkbox = () => {
return (
<div>
<label htmlFor="checkbox">Check</label>
<input id="checkbox" type="checkbox" />
</div>
)
}
export default Checkbox
我们想测试当用户单击复选框的关联标签时,输入的 "checked" 属性是否被正确设置。让我们看看如何编写一个测试用例
test('handles click correctly', async () => {
render(<Checkbox />)
const user = userEvent.setup()
// You can also call this method directly on userEvent,
// but using the methods from `.setup()` is recommended.
await user.click(screen.getByText('Check'))
expect(screen.getByLabelText('Check')).toBeChecked()
})
很好!
在测试中触发类方法 (wrapper.instance()
)
正如我们已经讨论过的,我们建议不要测试实现细节和用户不会知道的细节。我们的目标是用更接近用户的方式测试和交互组件。
如果您的测试使用`instance()`或`state()`,请注意您正在测试用户不可能知道或甚至不在乎的东西,这将使您的测试更难以让您确信在用户使用它们时会正常工作。—— Kent C. Dodds
如果您不确定如何测试组件内部的东西,请退一步考虑:“用户会怎么做才能触发此代码运行?”然后让您的测试执行此操作。
如何进行 `shallow` 渲染组件?
一般来说,您应该避免模拟组件。但是,如果您需要,请尝试使用 Jest 的模拟功能。有关更多信息,请参见 FAQ。