原文地址: medium.com/codeclan/te…
译文地址:github.com/xiao-T/note…
本文版权归原作者所有,翻译仅用于学习。
这篇文章将会介绍如何设置并使用 Jest 和 Enzyme 测试通过 Create React App (CRA) 创建的 React 应用。对于那些从头开始的人我们会给出一些建议。但是,不会涉及太多有关 React 的知识。
Jest 和 Enzyme 是两个不同,但又相互相成的工具,整合在一起可以提供更加灵活和更具创造性的测试能力。我们将会简单介绍两者有什么差异。
Jest
Jest 是一个 Javascript 单元测试框架,Facebook 用来测试服务和 React 应用。
CRA 已经内置了 Jest;不再需要单独安装。
Jest 是一个 测试运行器、断言库 和 模拟库。
Jest 也提供 快照测试,它可以创建一个组件的快照,然后与上一次保存快照对比。如果,前后两者不匹配,测试就会失败。快照将会被保存在名为 __snapshots__
文件夹中,它与被测试的文件在同一目录中。快照看起来就像下面这个样子:
exports[`List shallow renders correctly with no props 1`] = `
<List
ordered={false}
>
<ListItem>
Item 1
</ListItem>
<ListItem>
Item 1
</ListItem>
</List>
`;
复制代码
快照测试必须结合浏览器测试,并且,在一开始就要校验快照,以保证快照能按照期望的输出。
Enzyme
Enzyme 是针对 React 的测试功能库,它可以更容易的断言、操控和遍历 React 组件。
Enzyme,由 Airbnb 创建,并添加了更好的功能方法,比如:渲染组件、查找元素和与元素交互。
在 CRA 中它必须单独安装。
Jest 和 Enzyme
- Jest 和 Enzyme 两者都可以测试 React 应用,Jest 还可以测试其他的 Javascript 应用,但是 Enzyme 只能测试 React。
- Jest 可以单独渲染组件和快照测试,Enzyme 只是添加了更简单的方法。
- 没有 Jest,Enzyme 也可以使用,但是必须结合另外的测试运行器(runner)。
综上所述:
- Jest 作为测试运行器,断言库和模拟器使用
- Enzyme 提供额外的测试功能用与交互。
设置
安装和配置
如果不使用 CRA,需要安装 Jest:
npm install --save-dev jest babel-jest
复制代码
安装 Enzyme:
npm install --save-dev enzyme enzyme-adapter-react-16 enzyme-to-json
复制代码
更新 package.json
:
"jest": {
"snapshotSerializers": ["enzyme-to-json/serializer"]
}
复制代码
相比 Enzyme,为了更加方便的对比快照,enzyme-to-json
提供了更好组件格式化方式。当使用快照时 snapshotSerializers
可以更大限度的压缩重复代码。如果没有指定序列化工具,测试中每次对比快照是否匹配时都需要把组件传递给 enzyme-to-json
的 .toJson()
方法,反之,就不需要。
expect(toJson(rawRenderedComponent)).toMatchSnapshot();
复制代码
通过在 package.json
中添加这一配置,在你调用 Jest 的 .toMatchSnapshot()
时就不需要在调用 JSON 格式化方法了。
在 ./src/setupTests.js
创建 setupTets.js
文件:
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';configure({ adapter: new Adapter() });
复制代码
CRA 会自动搜索这个文件,如果,你没有使用 CRA,这时你就需要在 snapshotSerializers 的相同位置添加这一配置:
"setupFiles": ["./src/setupTests.js"],
复制代码
创建测试文件
Jest 将会在符合以下规则的地方查找所有的测试文件:
- 文件夹
__tests__
中以.js
结尾的文件 - 以
.test.js
结尾的文件 - 以
.spec.js
结尾的文件
比较方便的是把每个测试文件和需要测试代码放在同一目录下。这在语义上更加有意义,同时也会让相对路径更加简单(./MyComponent
vs ../../MyComponent
)。
以下是 MyComponent.test.js
的演示:
import React from 'react';
import { shallow } from 'enzyme';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('should render correctly in "debug" mode', () => { const component = shallow(<MyComponent debug />);
expect(component).toMatchSnapshot();
});
});
复制代码
在 CRA 环境下,当执行 npm test
时会运行所有的测试文件并把结果输出到终端。通过自定义标签 -- --testPathPattern filename/
可以指定只运行特定的文件或者使用 -- --testPathIgnorePatterns filename/
来忽略指定的文件。
Mount, Shallow, Render
import { mount, shallow, render } from ‘enzyme';
复制代码
为了测试组件以上的方法肯定会用到,就如上一段代码所示。
Mounting
- 完整的 DOM 渲染,包括子组件
- 对于需要和 DOM 交互、完整测试组件生命周期情况,这是最理想渲染方式
- 因为是真实把组件渲染成 DOM,为了不影响其他的测试,每次测试完成都需要调用
.unmount()
- 允许直接访问传递给 root 组件(包括默认的)和传递给子组件的 props。
Shallow
- 只渲染单个组件,不包括子组件。这对于需要隔离组件做单纯的单元测试非常有用。它可以防止因为子组件的改变和 bugs 影响组件测试的结果。
- 在 Enzyme 3 中
shallow
渲染默认也可以访问组件的生命周期方法。 - 不能访问传递给 root 组件的 props(因为它们不是默认 props),但是,它们可以传递给子组件,也可以测试传递的 props 是否对组件有影响。也就说
shallow
可以测试组件的渲染,但不能测试其中的 element。
Render
- 把组件渲染成静态的 HTML,包括子组件
- 不能访问 React 的生命周期方法
- 相比
mount
成本更低,但功能也更少
Testing
基本的组件渲染
一个简单无交互的组件:
it('should render correctly with no props', () => {
const component = shallow(<MyComponent/>);
expect(component).toMatchSnapshot();
});
it('should render banner text correctly with given strings', () => {
const strings = ['one', 'two'];
const component = shallow(<MyComponent list={strings} />);
expect(component).toMatchSnapshot();
});
复制代码
事件
Enzyme API 有好几种方式可以模拟事件或者用户交互。如果,你想测试子组件的交互功能,这时就需要 mount
方法了。
it('should be possible to activate button with Spacebar', () => {
const component = mount(<MyComponent />);
component
.find('button#my-button-one')
.simulate('keydown', { keyCode: 32 });
expect(component).toMatchSnapshot();
component.unmount();
});
复制代码
模拟方法
或许你只是想简单验证下通过 prop 传递的方法是否成功执行。
const clickFn = jest.fn();
describe('MyComponent', () => {
it('button click should hide component', () => {
const component = shallow(<MyComponent onClick={clickFn} />);
component
.find('button#my-button-two')
.simulate('click');
expect(clickFn).toHaveBeenCalled();
});
});
复制代码
慢慢会变得更加复杂,你或许想模拟 MyComponent.js
里面 import 的一个方法,它返回一个值,检查是否被调用,并对比快照。
想象一下,我们在 MyComponent.js
中引入了一个 SaveToStorage 对象,它包含 TryGetValue
和 TrySetValue
两个方法。TryGetValue
有一个默认的返回值:false,如果,它返回 true
说明组件会改变。组件中两个不同按钮会使用它们。
我们用 jest.mock
也可以模拟这种行为,同时,jest.fn
可以覆写组件内的方法。
const mockTryGetValue = jest.fn(() => false);
const mockTrySetValue = jest.fn();
jest.mock('save-to-storage', () => ({
SaveToStorage: jest.fn().mockImplementation(() => ({
tryGetValue: mockTryGetValue,
trySetValue: mockTrySetValue,
})),
}));
describe('MyComponent', () => {
it('should set storage on save button click', () => {
mockTryGetValue.mockReturnValueOnce(true);
const component = mount(<MyComponent />);
component.find('button#my-button-three').simulate('click');
expect(mockTryGetValue).toHaveBeenCalled();
expect(component).toMatchSnapshot();
component.unmount();
});
});
复制代码
总结
在这,只介绍了其中一部分,Jest 还有其它的断言方法(包括:toEqual
),同时 Enzyme 还有更多的遍历和交互的方法(包括:first
和 setProps
)。每个文档介绍的都很好,并提供了更多的可能。
同时使用 Jest 和 Enzyme 让测试 React 组件更加容易,同时测试更具可读性。
多谢你们喜欢!🙂
如果,你喜欢这篇文章,你还可以看看这些:
- 用 Jest 和 Enzyme 测试 React II
- 用 jest.mock() 模拟 ES 和 CommoJS 模块
- 使用 jest, jest-axe, 和 react-testing-library 测试 React 应用
- 用 Nock 模拟 HTTP 请求
资源
除了官方文档和我自己的经验,我发现下面两篇文章也非常有用,并且,我写的这篇文章也受到它们的启发同时也巩固了我的知识:
除单元测试和集成测试之外,你还希望看看视觉回归测试,我推荐你关注 Differencify。