前端单元测试库jest和enzyme介绍

我们可以通过完善单元测试来提高代码质量,并在一定程度上提高开发效率。

常见的 JS 单元测试框架有 mocha,jesmine,chai,Jest 等。

jest

简介

Jest 是 Facebook 开源的一款 JS 单元测试框架,它也是 React 目前使用的单元测试框架。 目前除了 Facebook 外,Twitter、Nytimes、Airbnb 也在使用 Jest。Jest 除了基本的断言和 Mock 功能外,还有快照测试、实时监控模式、覆盖度报告等实用功能。 同时 Jest 几乎不需要做任何配置便可使用。

1
npm install -D jest

测试文件名字格式为 **.test.js,我们可以建一个_test _文件夹来存放测试文件。同时,快照也会生成到该文件夹下。

package.json文件中增加脚本:

1
2
3
"scripts": {
"test": "jest --coverage --colors"
},

下次启动直接 npm test 就可以啦。

测试某个单独页面:npm test – src/js/dir1/xxx.test.js

测试某个单独页面并更新快照:npm test – src/js/dir1/xxx.test.js -u

配置

jest的配置文件名为 jest.config.js 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// jest.config.js
module.exports = {
verbose: true,
moduleFileExtensions: ['js', 'jsx'], //
testRegex: 'sum.test.js', // 只测试某些文件
// snapshotSerializers: ['enzyme-to-json/serializer'],
coveragePathIgnorePatterns: [],
testPathIgnorePatterns: ['/node_modules/']
};


// jest.config.js
module.exports = {
verbose: true,
moduleFileExtensions: ['js', 'jsx'],
moduleNameMapper: {
'^common(.*)$': '<rootDir>/src/static/js/util/common$1',
'^widget(.*)$': '<rootDir>/src/static/js/util/widget$1',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/__mocks__/fileMock.js',
'\\.(css|less|scss)$': '<rootDir>/__mocks__/styleMock.js',
'(^|./)css': '<rootDir>/__mocks__/styleMock.js'
},
snapshotSerializers: ['enzyme-to-json/serializer'],
coveragePathIgnorePatterns: [],
testPathIgnorePatterns: ['/node_modules/']
};

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

beforeAll(() => console.log('1 - beforeAll')); // 当前文件中所有测试执行前触发,只执行一次
afterAll(() => console.log('1 - afterAll')); // 当前文件中所有测试执行结束后触发,只执行一次
beforeEach(() => console.log('1 - beforeEach')); // 当前文件中每个测试执行前都会触发;
afterEach(() => console.log('1 - afterEach')); // 当前文件中每个测试结束后都会触发

// describe的作用就是对某一块进行分组,每一块属于不同的作用域,当上面的四个周期函数放到块中时,就只适用于该describe块内的测试。
describe('dir1 test', () => {

test('1+1 = ',() => {
expect(sum(1,2)).toBe(3);
});

test('测试对象是否相等',()=>{
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1,two: 2 });
});

test('快照测试 ', () => {
const user = {
// createdAt: new Date(),
// id: Math.floor(Math.random() * 20),
id: '234',
name: 'LeBron James'
};
expect(user).toMatchSnapshot(); // 生成快照
});

});

断言

Matchers ,也叫断言库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
expect({a:1}).toBe({a:1})			//判断两个对象是否相等
expect(1).not.toBe(2) //判断不等
expect(n).toBeNull(); //判断是否为null
expect(n).toBeUndefined(); //判断是否为undefined
expect(n).toBeDefined(); //判断结果与toBeUndefined相反
expect(n).toBeTruthy(); //判断结果为true
expect(n).toBeFalsy(); //判断结果为false
expect(value).toBeGreaterThan(3); //大于3
expect(value).toBeGreaterThanOrEqual(3.5); //大于等于3.5
expect(value).toBeLessThan(5); //小于5
expect(value).toBeLessThanOrEqual(4.5); //小于等于4.5
expect(value).toBeCloseTo(0.3); // 浮点数判断相等
expect('Christoph').toMatch(/stop/); //正则表达式判断
expect(['one','two']).toContain('one'); //数组中是否含有某个元素

Jest为我们提供了expect函数用来包装被测试的方法并返回一个对象,该对象中包含一系列的匹配器来让我们更方便的进行断言,

快照测试

快照测试是 Jest 内置了一种很有用的测试方式。快照测试可以用于保证界面不出现异常变化。 快照测试的基本原理是,渲染页面然后截图,将得到到截图与样本图片进行对比,以此来检查渲染是否符合预期。 两张图片对比不一致时,也有可能是预期发生了变化,这时就需要更新样本图片。

实际测试中,并不是必须对比图片,样本也可以是一份状态描述的字符串,这时只要对比序列化的字符串便可以验证渲染逻辑。

实际使用时,并不需要手工编写快照样本,Jest 可以在首次执行时可以自动生成快照样本,当测试代码有变化时,还可以通过 jest -u 来更新样本。 生成的样本会放在测试文件同级目录下的 snapshots 文件夹中。

enzyme

Enzyme是由Airbnb开源的一个React的JavaScript测试工具,使React组件的输出更加容易推断。Enzyme的API和jQuery操作DOM一样灵活易用。

安装:

1
npm i --save-dev enzyme@3.9.0  enzyme-adapter-react-16.2@1.5.2

渲染方式

enzyme支持三种方式的渲染: 浅渲染 (shallow)、完全渲染 (mount)、静态渲染 (render)

shallow:浅渲染,是对官方的Shallow Renderer的封装。将组件渲染成虚拟DOM对象,只会渲染第一层,子组件将不会被渲染出来,因而效率非常高。不需要DOM环境, 并可以使用jQuery的方式访问组件的信息;

render:静态渲染,它将React组件渲染成静态的HTML字符串,然后使用Cheerio这个库解析这段字符串,并返回一个Cheerio的实例对象,可以用来分析组件的html结构。

mount:完全渲染,它将组件渲染加载成一个真实的DOM节点,用来测试DOM API的交互和组件的生命周期,用到了jsdom来模拟浏览器环境。

1
2
3
4
5
- render采用的是第三方库Cheerio的渲染,渲染结果是普通的html结构

- shallow和mount对组件的渲染结果不是html的dom树,而是react树 。shallow和mount的结果是个被封装的ReactWrapper,可以进行多种操作,譬如find()、parents()、children()等选择器进行元素查找;state()、props()进行数据查找,setState()、setprops()操作数据;simulate()模拟事件触发

- shallow只渲染当前组件,只能对当前组件做断言;mount会渲染当前组件以及所有子组件,对所有子组件也可以做上述操作。一般交互测试都会关心到子组件,此时使用mount。但是mount耗时更长,内存占用的更多,如果没必要操作和断言子组件,可以使用shallow

API

Shallow Rendering: 适用于当没有和DOM互动,不涉及子组件时

1
2
3
4
import { shallow } from 'enzyme';

const wrapper = shallow(<MyComponent />);
// ...

Full Rendering

1
2
3
4
import { mount } from 'enzyme';

const wrapper = mount(<MyComponent />);
// ...

Static Rendering

1
2
3
4
import { render } from 'enzyme';

const wrapper = render(<MyComponent />);
// ...

常用的enzyme api方法:

1
2
3
4
5
6
7
.get(index):返回指定位置的子组件的DOM节点
.at(index):返回指定位置的子组件
.text():返回当前组件的文本内容
.html():返回当前组件的HTML代码形式
.props():返回根组件的所有属性
.prop(key):返回根组件的指定属性
.state([key]):返回根组件的状态

例子

Enzymetest.js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React from 'react';

export default class EnzymeTest extends React.Component {

constructor(props) {
super(props);
this.state = {
title: '标题1',
};
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
this.setState({ title: '标题2'});
console.log('执行.');
};
render() {
const { name } = this.props;
const { title } = this.state;
return (
<div>
<h1>{title}</h1>
<button onClick={this.handleClick}>{name}</button>
</div>
);
}
}

enzyme.test.js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from 'react'
import Enzyme,{ mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import EnzymeTest from '../EnzymeTest';

Enzyme.configure({ adapter: new Adapter() });

describe('Enzyme shallow', ()=> {

test('Example component', ()=> {

const name='确定';
const wapper = mount( <EnzymeTest name={name} />);

expect(wapper.find('h1').text()).toBe('标题1');
expect(wapper.find('button').text()).toBe('确定');

wapper.find('button').simulate('click');
expect(wapper.find('h1').text()).toBe('标题2');

wapper.setProps({ name: '取消' });
expect(wapper.find('button').text()).toBe('取消');
})
});