微信号:frontshow

介绍:InfoQ大前端技术社群:囊括前端、移动、Node全栈一线技术,紧跟业界发展步伐。

开启Enzyme的React测试之旅

2019-06-13 16:17 Diogo Souza

作者 | Diogo Souza
译者 | 刘雅梦

测试是 Web 应用程序开发中的一个重要步骤,特别是在诸如 React 之类的组件库之上构建的应用程序。在众多不同的测试工具和测试库中 Enzyme 因其灵活性和易于开发 React 代码的测试而脱颖而出。

实际上,Enzyme 更像是一个 JavaScript 测试工具,它使断言、操作和遍历 React 组件的输出变的更容易。它由 Airbnb 创建,添加了许多实用功能,以便以类似于 jQuery 的方式方便组件渲染、查找和与元素交互。

在本教程中,我们将以一些实际的示例来探索该实用程序提供的不同渲染选项。

相关链接:

Enzyme: https://airbnb.io/enzyme/

安装设置

首先,确保您本地开发机上的 Node 版本不低于 V8.10.0。如果不确定您使用的是否是最新版本,可以参阅 nvm 文档进行确认: https://github.com/nvm-sh/nvm#installation

在本项目中,我们将使用 create-react-app 脚手架来搭建一个 React 应用程序并立即运行它。有了它,我们不需要安装或配置像 Webpack 或 Babel 之类的工具;它们是预先配置且透明的,因此,我们可以更专注于代码。

它使用了 npx ,npx 是一个 npm V5.2 及之后版本自带的 npm 包运行器,因此整个过程将非常简单。您可以在这里阅读更多关于 npx 的内容: https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b

因此,为了构建我们的应用程序,请在提示下运行如下命令:

cd your-project-directory
npx create-react-app logrocket-react-enzyme-app

它将创建并初始化一个预置了多个 Node 模块的项目,这些模块对 React 的开发非常重要:

使用 Facebook 的 create-react-app 构建应用程序输出

生成项目的文件目录结构:

logrocket-react-enzyme-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── serviceWorker.js

打开   package.json 文件分析发现,它预安装的 Node 依赖包括 React、 React DOM 及一些用于启动程序、构建测试等的预设脚本。

{
"name": "logrocket-react-enzyme-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

logrocket-react-enzyme-package.json: https://gist.github.com/diogosouza/250abbd7a9c9a77b4f01c4a920cfd735#file-logrocket-react-enzyme-package-json

在应用程序根目录下输入 npm start 命令,并在浏览器中输入 http://localhost:3000/ 打开首页,即可查看正在运行的应用程序,就像这样:

React 应用程序首页

现在,我们开始 Enzyme 的安装。如果我们在应用程序中使用了 React 16(本文写作时的最新版本),可以使用如下命令来正确地安装 Enzyme:

npm i — save-dev enzyme enzyme-adapter-react-16

运行命令后,您会发现 package.json 已更新:

"devDependencies": {
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.12.1"
}

如果您使用的是其他版本的 React,也无需担心,可以按照 官方 Enzyme 安装文档 的指导来为您的项目安装正确的版本。

注意:如果您使用的是 Linux 系统,并且出现这样的错误 Error: ENOSPC: System limit for number of file watchers reached ,则表示系统的文件监视程序已达到限制。修复该错误,仅需运行以下命令:

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
创建第一个测试用例

为了使用 Enzyme 的功能,我们需要让我们的 React 应用程序感知到 Enzyme 已安装并是可用的。我们可以参考之前安装的适配器包,正确设置 Enzyme 使用的适配器。因此,转到 src 目录下,并创建一个名为 enzyme.js 的新文件:

import Enzyme, { configure, shallow, mount, render } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });
export { shallow, mount, render };
export default Enzyme;

logrocket-react-enzyme-enzyme.js: https://gist.github.com/diogosouza/514759766574a45d1d4d36109caff49f#file-logrocket-react-enzyme-enzyme-js)

这足以输出所有不同的 Enzyme 渲染类型。然后,在 src/components 目录中创建另一个文件夹 tests ,以保存我们的测试文件。

让我们考虑一个简单的例子:一个 HTML 有序列表。它由两部分组成:

  • OrderedList :它将存储用于构建 OrderedListOption 元素中的 <ol>的函数,以检查不同的显示配置下列表是否为空。

  • OrderedListOption : 它单独处理每个  <li>项、CSS 类和其值。


import React from 'react';
import PropTypes from 'prop-types';

import OrderedListOption from './OrderedListOption';

function OrderedList(props) {
const { options } = props;
if (!options.length) {
return <span className="empty">No options added.</span>;
}

return (
<ol className="options">
{options.map(option => <OrderedListOption key={option} value={option} />)}
</ol>
);
}

OrderedList.propTypes = {
options: PropTypes.array,
};

OrderedList.defaultProps = {
options: [],
};

export default OrderedList;

logrocket-react-enzyme-OrderedList.js: https://gist.github.com/diogosouza/58387bf2b6ad3c780fce362aa4eaa8b8#file-logrocket-react-enzyme-orderedlist-js

代码非常简单:我们导入 React 和位于文件顶部的 OrderedListOption 组件,同时检查用以显示正确 HTML 内容的元素数组是否为空。如果数组不是空的,我们将遍历它来构成列表选项。

看下 OrderedListOption 的代码:

import React from 'react';
import PropTypes from 'prop-types';

function OrderedListOption(props) {
const { value } = props;
return <li className="value">{value}</li>;
}

OrderedListOption.propTypes = {
value: PropTypes.string,
};

export default OrderedListOption;

logrocket-react-enzyme-OrderedListOption.js: https://gist.github.com/diogosouza/8a92799efd43932686c9d519c61fe207#file-logrocket-react-enzyme-orderedlistoption-js

该代码仅接收元素的值并将其放入 <li>元素中。很简单,不是吗?

在我们创建测试之前,有必要说明下 Enzyme 支持的三种渲染类型。

shallow

shallow 基本上每次仅渲染第一层组件。换句话说,Enzyme 不会渲染测试的子元素。考虑这些情况,如果您希望在与周围或内部隔离的情况下, 仅测试组件本身。你会更喜欢单元测试而不是完整的集成测试,这种渲染类型会非常有用。

mount

这种渲染方式和 shallow 方式相反,需要使用包含全部子元素的完整 DOM 结点来进行渲染。它适用于每个组件与其他组件(如 DOM API)密集交互的情况。

render

它将组件渲染成 HTML 静态文件,该静态文件包含所有的子元素。同时,它阻止了对 React 生命周期方法的访问,这反过来,又为测试提供了更少的灵活性和功能 。此外,它速度更快。它建立在 Cheerio(详情见下方链接) 之上,Cheerio 是一个基于 jQuery 核心功能在服务器上对 DOM 进行操作和遍历的 API。因此,您将掌握 jQuery 的所有功能。

相关链接:

Cheerio: https://github.com/cheeriojs/cheerio

现在我们开始做测试。在 /tests 文件夹中创建名为 orderedlist.test.js 的新文件,并添加以下代码:

import React from 'react';
import { shallow, mount, render } from '../../enzyme';

import OrderedList from '../OrderedList';

describe('Our test suite', () => {

it('renders all the mocked animal options', () => {
const animals = ['duck', 'bear', 'whale'];

const wrapper = render(<OrderedList options={animals} />);

expect(wrapper.find('.options')).toBeDefined();
expect(wrapper.find('.value')).toHaveLength(animals.length);
});

it('renders no animal options', () => {
const animals = [];
const wrapper = shallow(<OrderedList options={animals} />);

expect(wrapper.find('.empty').exists()).toBe(true);
});

it('renders a single animal option', () => {
const animals = ['duck'];
const wrapper = mount(<OrderedList options={animals} />);

expect(wrapper.contains(<li key='duck' className="value">duck</li >)).toBeTruthy();
});

it('renders correct text in animal option', () => {
const animals = ['duck', 'bear', 'whale'];
const wrapper = mount(<OrderedList options={animals} />);

expect(wrapper.find('.value').get(0).props.children).toEqual('duck');
});
});

logrocket-react-enzyme-OrderedList.test.js: https://gist.github.com/diogosouza/af40878ce0bcfdcbbfddb3e642eb626f#file-logrocket-react-enzyme-orderedlist-test-js

首先,我们向之前创建的 enzyme.js 文件中导入文件开头的三种渲染类型。在此,我们针对每种渲染类型设计了四种测试场景。

第一个测试是针对 render 类型的,我们基本上是按照给定的动物参数数组请求 Enzyme 来渲染 OrderedList,并通过 expect() 函数断言测试条件。

wrapper 对象用以呈现 render() 的结果,在该结果中,我们可以调用 find 函数查找与数列内部元素相关的 CSS 类的 options(元素的子项)和 value。也可以测试子元素的数量。

第二个测试将重点放在不接收任何元素的列表上。在此,我们使用的是 shallow 渲染类型,这使我们可以使用诸如 exists() 之类的方法。

最后两个测试使用了 mount() 函数,该函数将完整的 DOM 返回给 wrapper 对象。contains() 方法是另一个 React 生命周期方法。

测试表单组件

更进一步地,我们可以测试表单、表单元素、事件等内容。让我们看下第二个示例,一个登录表单组件(Login.js):

import React from 'react';

class Login extends React.Component {
constructor() {
super()
this.state = {
username: '',
password: ''
}
}

handleInputChange = (event) => {
this.setState({
[event.target.name]: event.target.value
})
}

render() {
return (
<form className='login'>
<label>Username</label>
<input id='email' onBlur={this.handleInputChange} name='email' type='text' />
<label>Password</label>
<input id='password' onBlur={this.handleInputChange} name='password' type='password' />
<button>Submit</button>
</form>
)
}
}
export default Login

logrocket-react-enzyme-Login.js: https://gist.github.com/diogosouza/43a14d74199dc1f0d248036339318d4f#file-logrocket-react-enzyme-login-js

这是一个常见的表单结构组件,此外,我们需要在每个输入框发生模糊事件时,更新保存在这里的状态。

我们来看下 Login.test.js 文件:

import React from 'react';
import { shallow, mount, render } from '../../enzyme';
import Login from '../Login'

describe('Login Test Suite', () => {

it('should render the form', () => {
const wrapper = shallow(<Login />);

expect(wrapper.find('form.login').exists()).toBe(true);
expect(wrapper.find('#email').length).toEqual(1);
expect(wrapper.find('#password').length).toEqual(1);
})
})

describe('Email Test Suite', () => {

it('should change the state of the Login component', () => {

const wrapper = shallow(<Login />);
wrapper.find('#email').simulate('blur',
{
target: { name: 'email', value: 'logrocket@mail.com' }
});

expect(wrapper.state('email')).toEqual('logrocket@mail.com');
})
})

describe('Password Test Suite', () => {

it('should change the state of the Login component', () => {

const wrapper = mount(<Login />);
wrapper.find('#password').simulate('blur',
{
target: { name: 'password', value: 'my log is rocket' }
});

expect(wrapper.state('password')).toEqual('my log is rocket');
})
})

logrocket-react-enzyme-Login.test.js: https://gist.github.com/diogosouza/843ebe3fdfd95021714fb24a5d6acc47#file-logrocket-react-enzyme-login-test-js

第一个测试组件并不新鲜,我们只是检查了表单元素是否存在。第二和第三个测试使用 simulate() 函数模拟(顾名思义)字段的事件 , 在本例中模拟的是 onBlur 事件。

一旦我们设置了 onBlur,都将触发每个输入字段的状态更新,我们就可以检查是否存储了相同的状态。这是一个很好的行为测试示例,通过它,我们可以测试在 Enzyme 强制模拟组件事件后将会发生什么。

这是在 IDE 中的最终输出(此处为 Visual Studio 代码):

终端测试结果

总结

您可以从我的 GitHub 仓库访问完整的源代码。如果您喜欢,请留下星级评分。GitHub 仓库地址: https://github.com/diogosouza/react-enzyme-app

在这里,我们仅介绍了 Enzyme 提供的众多方法和示例中的一部分。这是一个动态且丰富的环境,您可以创建测试套件并探索许多不同的测试场景,例如集成,统一,行为,语义等。

参阅官方文档页面: https://airbnb.io/enzyme/docs/api/ 了解更多关于 Enzyme 及其功能、其他配置等信息。使用它构建良好测试的应用程序,使其更值得信赖。

你用过 Enzyme 来测试 React 了吗?请将相关体验信息反馈给我们。

英文原文: https://blog.logrocket.com/getting-started-with-enzyme-for-react-a106b58fc53b

 
前端之巅 更多文章 如何将Web主页性能提升十倍以上? Flutter VS React Native,应该选哪个? 2020年你应该知道的8种前端JavaScript趋势和工具 未来PWA将取代本地应用,\\b成为构建良好用户体验的首选方式 WHATW击败W3C,赢得HTML和DOM的控制权
猜您喜欢 硅谷银行分析报告“大数据的下一步棋:把握大数据的前景” 【设计谈】色彩 自拍:一张价值一万亿的自拍 马云柳传志等大佬合影 陌生网络里的恐惧(终篇) 游戏开发中的网络通信