用Mocha + chai做React + Redux的单元测试

当面对现在流行的React+Redux创建的Web应用时,该如何用Mocha+Chai进行测试?

搭建测试基础框架

为了搭建搭建框架,我们需要做以下几个事:

  1. 设置一个类浏览器的命令行测试环境
  2. 创建一个‘renderComponent’函数来render react class
  3. 模拟事件simulating events
  4. 设置chai-jquery

下面详细解释这几个事情

搭建一个类浏览器的命令行测试环境

使用自动化工具(Webpack,browserify)构建出来的web应用,几乎所有的内容都浓缩在bundle.js里了。
不同于浏览器,terminal环境要解析Ract中的DOM,以及一些全局变量就需要工具了,

这里我们采用jsdom来模拟DOM结构。
再将这个DOM结构封装成jquery对象。

1
2
3
4
5
6
import jsdom from 'jsdom';
import jquery from 'jquery';

global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = global.document.defaultView;
const $ = jquery(global.window);

创建一个‘renderComponent’函数来render react class

写React应用时最常用的方法render就是将React的组件渲染成DOM(或者虚拟DOM),所以在测试之前,我们创建一个renderComponent方法,将React的组件渲染出来。

用ReactTestUtils的renderIntoDocument方法,将一个组件渲染成DOM node。

1
2
3
ReactComponent renderIntoDocument(
ReactElement instance
)

当我们使用Redux的时候,需要用Provider来渲染组件,参数分别为组件的props和Redux的state(store),因为Redux只有一个store,所以使用起来还是比较方便的。
生成的组件instance用findDOMNode方法获取真实DOM节点,并绑定成jquery对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import TestUtils from 'react-addons-test-utils';
import ReactDOM from 'react-dom';
import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducers from '../src/reducers'; //这里路径定义到自己的reducer文件夹
function renderComponent(ComponentClass, props, state) {
// React Add-Ons : renderIntoDocument
const componentInstance = TestUtils.renderIntoDocument(
<Provider store={createStore(reducers, state)}>
<ComponentClass {...props} />
</Provider>
);

return $(ReactDOM.findDOMNode(componentInstance));
}

这样,通过renderComponent方法,就可以将一个react组件转换成dom节点并返回。

模拟事件simulating events

有时候,我们需要模拟一些事件来满足我们的测试需求,例如点击button,按下键盘,输入文字等。
这部分我们用ReactTestUtils的simulate方法来实现。

1
2
3
4
5
6
$.fn.simulate = function(eventName, value) {
if (value) {
this.val(value);
}
TestUtils.Simulate[eventName](this[0]);
}

设置chai-jquery

设置一下chai-jquery,具体参看官方网页。

1
2
3
import chai, { expect } from 'chai';
import chaiJquery from 'chai-jquery';
chaiJquery(chai, chai.util, $);

export方法

最后不要忘了把renderComponent和chai的expect export出去

1
export { renderComponent, expect };

test_helper.js

以上内容可以合成到一个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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import jsdom from 'jsdom';
import jquery from 'jquery';
import TestUtils from 'react-addons-test-utils';
import ReactDOM from 'react-dom';
import chai, { expect } from 'chai';
import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducers from '../src/reducers';
import chaiJquery from 'chai-jquery';

// 设置一个类浏览器的命令行测试环境
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = global.document.defaultView;
const $ = jquery(global.window);

// 创建一个‘renderComponent’函数来render react class

function renderComponent(ComponentClass, props, state) {
// React Add-Ons : renderIntoDocument
const componentInstance = TestUtils.renderIntoDocument(
<Provider store={createStore(reducers, state)}>
<ComponentClass {...props} />
</Provider>
);

return $(ReactDOM.findDOMNode(componentInstance));
}


// 模拟事件simulating events
$.fn.simulate = function(eventName, value) {
if (value) {
this.val(value);
}
TestUtils.Simulate[eventName](this[0]);
}

// set up chai-jquery
chaiJquery(chai, chai.util, $);

export { renderComponent, expect };

如何使用test_helper.js

说了半天只不过写了一个测试工具框架而已,下面说说怎么使用这个工具。

假设我们有一个评论APP,有一个评论框,下面是评论列表
APP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react';
import { Component } from 'react';
import CommentBox from './comment_box';
import CommentList from './comment_list';

export default class App extends Component {
render() {
return (
<div>
<CommentBox />
<CommentList />
</div>
);
}
}

测试案例

  1. 有一个comment box (这里检查是否包含comment-box class)
  2. 有一个comment list (这里检查是否包含comment-list class)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import { renderComponent, expect } from '../test_helper';
    import App from '../../src/components/app';

    // 使用describe把相似的测试结合起来
    describe('App', () => {
    let component;

    beforeEach(() => {
    component = renderComponent(App);
    });

    // 使用it测试一个目标的一个行为
    it('shows a comment box', () => {
    // 使用expect断言一个目标
    expect(component.find('.comment-box')).to.exist;
    });

    it('shows a comment list', () => {
    expect(component.find('.comment-list')).to.exist;
    });
    });

再做一个CommentBox的测试案例:

  1. 有一个textarea
  2. 有一个button
  3. 在textarea中显示输入的文字
  4. 当提交后清空textarea
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
import { renderComponent, expect } from '../test_helper';
import CommentBox from '../../src/components/comment_box';

describe('CommentBox', () => {
let component;

beforeEach(() => {
component = renderComponent(CommentBox);
});

it('has a text area', () => {
expect(component.find('textarea')).to.exist;
});

it('has a button', () => {
expect(component.find('button')).to.exist;
});

describe('entering some text', () => {
beforeEach(() => {
component.find('textarea').simulate('change', 'new comment');
});

it('shows text that is entered', () => {
expect(component.find('textarea')).to.have.value('new comment');

});

it('when submitted, clears the input', () => {
component.simulate('submit');
expect(component.find('textarea')).to.have.value('');
});
});

});

如何跑测试

在package.json的script中添加测试命令

1
2
"test": "mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive ./test",
"test:watch": "npm run test -- --watch"

需要测试的时候

1
npm run test:watch

就可以了。

热评文章