## **利用Jest测试React组件**
> Jest 是一个由 facebook 维护的测试框架,在本文中,我们将利用 Jest 来测试 React 组件。我们将首先了解如何在纯 JavaScript 函数上使用 Jest,然后再了解它提供的一些开箱即用的特性,这些特性旨在使测试 React 应用程序变得更容易。
注意,Jest 并不是专门针对 React 的测试框架,你可以使用它来测试任何 JavaScript 应用程序。然而,它提供的一些特性对于测试用户界面非常方便,这就是它非常适合 React 的原因。
## **示例程序**
在可以进行测试前,我们需要一个应用程序,在此以一个待办事项列表为例,大家可以从 Github 中克隆该应用:testing-react-with-jest。
示例项目利用 webpack 进行构建,
应用程序的入口点是 `src/index.js`,它将 `<App>` 组件呈现到 HTML 中:
```js
import React from 'react'
import { render } from 'react-dom'
import App from './App.jsx'
render(
<App />,
document.getElementById('root')
)
```
应用程序中组件树层级结构如下:
应用中使用到的初始状态数据及业务逻辑在 `src/state/index.js` 中:
```js
export const initialTodos = [ ... ]
export const addTodoItem = (todos, todo) => { ... }
export const toggleTodoItem = (todos, id) => { ... }
export const removeTodoItem = (todos, id) => { ... }
```
UI 界面效果:
## **Jest使用**
Jest 于 2014 年首次发布,虽然它最初引起了很多人的兴趣,但该项目一度处于休眠状态。然而,Facebook 投入了大量精力来改进 Jest,随后发布了一些更新的版本,与最初的开源版本相比,Jest 的唯一相似之处是名称和徽标,其他一切都已更改和重写。
## **安装与配置Jest**
首先,我们需要安装 Jest,由于我们也在使用 Babel,所以一并安装相关的模块,使 Jest 和 Babel 开箱即用:
```js
$ npm install jest babel-jest --save-dev
# 或
$ yarn add jest babel-jest --dev
```
在项目根目录下创建 `__tests__` 目录,Jest 希望在一个`__tests__` 目录中找到我们的测试,这已经成为 JavaScript 社区的流行约定,当然,如果你对创建 `__tests__` 目录统一管理不感冒,Jest 也支持查找任意的 `*.test.js` 或 `*.spec.js` 文件。
下面我们来测试我们的状态函数。
继续创建 `__tests__/state/index.test.js`,在正式测试之前,我们先看是否能够正常进行测试:
```js
describe('数字加减算术运算', () => {
test('3加2减5等于0', () => {
expect(3 + 2 - 5).toBe(0)
})
})
```
在 `package.json` 文件中添加 `npm scripts` 任务:
```js
{
"scripts": {
"test": "jest"
}
}
```
然后在命令行中执行测试任务:
```js
$ npm test
```
运行结果:
在 Jest 中,我们可以根据需要使用 `describe` 和 `test` 嵌套测试,`describe` 创建一个将几个相关测试组合在一起的块,`test` 是运行测试的方法。在此测试中,使用了 `expect` 和 `toBe` 来检测两个值是否完全相同。相关 API 使用我们可以在 Jest 官方 API 文档 中进行查阅,本文中我们也可以来了解一些 Jest 的断言。
## **测试业务逻辑**
我们已经通过一个示例看到了 Jest 在测试中的工作,下面我们来测试状态逻辑中的一个函数 `removeTodoItem`:
```js
export const removeTodoItem = (todos, id) => todos.filter(todo => todo.id !== id)
```
`removeTodoItem` 接收要删除待办事项的当前状态与待删除待办事项的 id。
利用 `describe` 与 `test` 编写该测试:
```js
describe('待办事项操作测试', () => {
test('删除待办事项', () => {
})
})
```
可能大家也会看到其它文档中使用 `it` 来代替 `test`,其实 `it` 是作为 `test` 的别名使用的,二者是相同的作用。
下面开始编写断言,首先,可以创建一个初始状态 `todos` 数组,然后将它传递给 `removeTodoItem`,再传递我们想要删除的新的待办事项 id,`removeTodoItem` 会返回完成后的状态,可以如下定义:
```js
describe('待办事项操作测试', () => {
test('删除待办事项', () => {
const todos = [
{id: 1, title: '复习 React 基础知识', completed: true},
{id: 2, title: '复习 React Hooks 使用', completed: false}
]
const finishedTodos = removeTodoItem(todos, 1)
expect(finishedTodos).toEqual([{id: 2, title: '复习 React Hooks 使用', completed: false}])
})
})
```
在这里我们使用 `toEqual` 来做出断言,它对引用类型的对象将进行“深”比较是否相等。`toBe` 一般用于原始类型的值的比较,例如字符串、数字等类型,`toEqual` 通常在数组和对象上使用。
在命令行中执行 `npm test`:
可以看到测试通过。
## **测试React组件**
不建议在 React 组件上编写太多的测试,任何你想测试的内容,比如业务逻辑,还是建议从组件中独立出来放在单独的函数中,就像上边测试状态函数一样。但有时测试一些 React 交互是很有必要的,如要确保用户在单击某个按钮时使用正确的参数去调用特定函数。
*如果利用 Create React App 来创建项目,从 3.3 以后的版本中已自带官方推荐的 testing-library,本文暂不讨论 testing-library。*
## **安装与配置Enzyme**
为了编写我们的测试,先安装 Enzyme,这是一个由 Airbnb 编写的包装库,它使得测试 React 组件变得更容易。同时,我们还要为我们使用的 React 不同版本安装适配器,本例中使用 React v17.x,目前还没有官方适配器可用,但也有社区版本:
```js
$ npm install --save-dev enzyme @wojtekmaj/enzyme-adapter-react-17
# 或
$ yarn add --dev enzyme @wojtekmaj/enzyme-adapter-react-17
```
对于 React v16.x,可以使用 `enzyme-adapter-react-16`。
接着对 Enzyme 进行少量相关配置,在项目根目录下创建 `setup-enzyme.js` 文件:
```js
import { configure } from 'enzyme'
import Adapter from '@wojtekmaj/enzyme-adapter-react-17'
configure({ adapter: new Adapter() })
```
然后在项目根目录下创建 `jest.config.js` 文件:
```js
module.exports = {
setupFilesAfterEnv: [
'./setup-enzyme.js'
],
testEnvironment: 'jsdom'
}
```
这告诉 Jest 在执行任何测试之前,为我们运行 `setup-tests.js` 文件,由于测试的是 Web 应用程序,所以使用浏览器类似的环境 jsdom 来代替。
## **编写组件渲染测试**
接下来我们就可以编写测试了,我们来测试 `TodoItem` 组件是否能够在段落中渲染待办事项文本。
创建 `__tests__/components/TodoItem.test.js`:
```js
import React from 'react'
import { mount } from 'enzyme'
import TodoItem from '../../src/components/TodoItem.jsx'
describe('待办事项-列表项组件', () => {
test('渲染待办事项列表项', () => {
})
})
```
从 `enzyme` 中导入 `mount`,用于渲染组件,这样就可以检查输出并对其进行断言测试了。即使我们在 Node 环境中运行测试,也可以编写需要的 DOM 并进行测试,因为 Jest 中允许你使用 `jsdom` 库操作 DOM,这样就不必每次都启动浏览器来测试了。
接下来我们完成测试编写,整个测试如下:
```js
import React from 'react'
import { mount } from 'enzyme'
import TodoItem from '../../src/components/TodoItem.jsx'
describe('待办事项-列表项组件', () => {
test('渲染待办事项列表项', () => {
const todo = {id: 2, title: '复习 React Hooks 使用', completed: false}
const wrapper = mount(
<TodoItem todo={todo} />
)
const p = wrapper.find('p')
expect(p.text()).toBe('复习 React Hooks 使用')
})
})
```
简单说明一下,我们可以使用 `mount` 来创建 `TodoItem`,然后调用 `wrapper.find()` 传递一个 CSS 选择器来查找节点,由于示例的 DOM 很简单,所以直接使用 `p` 元素选择器查找,这和使用 `jQuery` 类似。最后,可以断言段落中的文本内容应该是“复习 React Hooks 使用”。
可以看到测试通过。
## **编写交互测试**
下面我们来测试当点击待办事项列表项中的段落时,修改待办事项的完成状态。
Jest 中提供了 **`spy`** (间谍)的开箱即用的功能,一个 `spy` 是一个函数,你不用关心它的实现,只需关心它的调用时间与调用方式,当我们要监视函数时可以考虑使用它。
要修改待办事项的完成状态,需要传递 `toggle` 属性,它是一个函数,我们现在来测试当用户点击时,该函数被使用正确的参数进行调用。
首先使用 `jest.fn()` 创建一个 `spy`:
```js
const toggle = jest.fn()
```
这为我们提供了一个可以监视并确保它被正确调用的函数。
接下来渲染 `TodoItem` 组件:
```js
const toggle = jest.fn()
const wrapper = mount(
<TodoItem todo={todo} toggle={toggle} />
)
```
然后查找 DOM 节点并模拟用户触发点击事件:
```js
const p = wrapper.find('p')
p.simulate('click')
```
`simulate()` 用于模拟用户事件。
剩下的就是断言 `spy` 函数被正确调用,整个测试应该如下:
```js
test('点击修改待办事项状态', () => {
const toggle = jest.fn()
const todo = {id: 2, title: '复习 React Hooks 使用', completed: false}
const wrapper = mount(
<TodoItem todo={todo} toggle={toggle} />
)
const p = wrapper.find('p')
p.simulate('click')
expect(toggle).toBeCalledWith(2)
})
```
我们期望使用待办事项的 id 作为参数进行调用,可以用 `expect(toggle).toBeCalledWith(2)` 来断言。这样,我们的测试就完成了:
测试通过。
## **总结**
Jest 被大量的使用,已成为 JavaScript 开发人员最爱的测试框架之一,相信它会变得更好。在测试方面,它拥有速度快、API 简单、易配置、IDE 整合、多项目并行等优势,相信随着大家的使用,你一定会爱上它的。
文章示例 Github 仓库:[https://github.com/itrainhub/testing-react-with-jest](https://link.zhihu.com/?target=https%3A//github.com/itrainhub/testing-react-with-jest)
**- End -**
更多关于“web前端培训”的问题,欢迎咨询千锋教育在线名师。千锋已有十余年的培训经验,课程大纲更科学更专业,有针对零基础的就业班,有针对想提升技术的提升班,高品质课程助理你实现梦想。