老实说,这篇文章只能是作为初学总结吧。
之前也看过Jasmine, 所以对API还是不陌生的。就总结下在自己的项目中如何写单元测试吧。

这是一个结合项目的学习心得

使用Jest

其实是因为Creact React App中默认集成了Jest测试框架,所以就直接拿来使用。
Getting Started

关于匹配器、异步代码测试、还有测试前后函数、Mock函数的API,官网文档都比较详细,在小项目中我还没有想到使用场景。
这里比较陌生的概念是Mock对象,Wiki上Mock对象的定义是:

In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. A computer programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behavior. of a human in vehicle impacts.

也就是说Mock测试是用虚拟的Mock对象来模拟真实对象,作为测试期间的替代品。

Snapshot Test

快照测试,在一些匹配对象的测试中,由于我们经常用如下步骤来进行测试(图来自2017年React Conf中Jest的演讲中):
image_1bngdpe889g7j5m1h5a4h6pjf9.png-53kB
过程当中一旦函数返回结构发生改变,需要重新Copy结果,进行测试。如果函数较少还可以手动操作,但比较多的时候呢。
就需要快照了,快照就是在初次运行时将结果存储到本地(JSON格式),在接下来的测试中跟本地快照进行对比即可。
如果函数接口有所改变,CLI 参数添加 -u,可重新生成一份快照。

enzyme

如果要进行DOM测试,就需要enzyme(React版本高于15.5)了。
enzyme有浅渲染和全渲染的概念。

  • 浅渲染:只渲染指定的React最上层组件,也就是对于子组件并不进行编译和渲染。如果子组件有错,测试也可以通过。返回ShallowWrapper对象,而且不需要一个浏览器环境,在测试单组件时比较方便。
  • 全渲染:对所有的组件进行编译和渲染,需要浏览器环境,在需要测试组件交互的时候能够使用到。返回ReactWrapper对象
    所以对于如下形式代码,会有如果结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
// App.js
function SubApp(props) {
return (<p>this is {props.name}</p>);
}

class App extends React.Component {
constructor(){
super();
}
render(){
return(<SubApp name='sub' />);
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// App.test.js
import React from 'react';
import ReactDOM from 'react-dom';
import {shallow, mount} from 'enzyme';
import App from './App'
test('shallow rendering',{
const app = shallow('<App />');
expect(app.find('SubApp')).toHaveLength(1); // TEST PASS
expect(app.find('p')).toHaveLength(1); // TEST FAIL
})
test('All DOM rendering',{
const app = mount('<App />');
expect(app.find('p')).toHaveLength(1); // TEST PASS
})

shallow()mount()是返回React element的一个wrapper对象,enzyme很多接口是在这个wrapper对象上实现的,比如DOM事件模拟simulate(event)
所以如果需要在wrapper对象上进行索引的化,需要区分at(index)get(index)

  • at(index) 返回ShallowWrapper对象
  • get(index) 返回ReactElement
  • find(selector) 选择器返回wrapper对象
  • simulate(event[, ...args]) 事件模拟并返回自身

enzyme提供了比较详细的API,都可以在官网上进行查询。

实践

这里我将自己比较简单的测试代码贴上,对基于React的Calculator App 进行了一个不全面的测试。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import React from 'react';
import ReactDOM from 'react-dom';
import {shallow, mount} from 'enzyme';
import Calculator from './Calculator';
import {optCalculator, formatNumber} from './Calculator';

test('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<Calculator />, div);
});

// test optCalculator
describe('optCalculator works normally', () => {
test('+ get right results', () => {
expect(optCalculator['+'](1, 2)).toBe(3);
});
test('- get right results', () => {
expect(optCalculator['-'](1, 2)).toBe(-1);
});
});

// test formatNumer
describe('formatNumber() works normally',() => {
test('localize big num with ,',() => {
expect(formatNumber(1234)).toBe('1,234');
expect(formatNumber(12345)).toBe('12,345');
});
test('with 6 fraction digits',() => {
expect(formatNumber(1.01234567)).toBe('1.012346');
});
});

// snapshot test
describe('snapshot test',() => {
test('snapshot test', () => {
expect(optCalculator['/'](4,2)).toMatchSnapshot();
});
});


// dom test
describe('DOM test',() => {
test('plus', () => {
// const calculator = shallow(<Calculator />);
const calculator = mount(<Calculator />);
const expression = calculator.find('.expression');
const mainDisplay = calculator.find('.mainDisplay');
expect(expression).toHaveLength(1);
expect(mainDisplay).toHaveLength(1);
const digitalKeys = calculator.find('.digitalKey');
expect(digitalKeys).toHaveLength(11);

// dom manipulation
const one = digitalKeys.at(6);
const two = digitalKeys.at(7);
// console.log('one',one);
const plus = (calculator.find('.operationKey')).at(3);
one.simulate('click');
plus.simulate('click');
two.simulate('click');
plus.simulate('click');
// click 1+1+
expect(expression.text()).toEqual('1+2+');
expect(mainDisplay.text()).toEqual('3');

});
});