前面介绍了Polymer的测试框架web-components-tester, 今天来看看React团队出品的Jest.在此,特别感谢婆婆帮忙带宝宝才让我有时间继续书写文章.
前面介绍了Polymer的测试框架web-components-tester, 今天来看看React团队出品的Jest.在此,特别感谢婆婆帮忙带宝宝才让我有时间继续书写文章.
Jest的功能
- 内置jasmine
- 内置mock函数 可以mock模块
- 基于jsdom
- 同步化书写异步代码
- 真心赞一下简洁明了的API定义和用法 以及清晰的文档,确实让书写单元测试不再痛苦
- 适用于commonJS模块的单元测试
- 运行速度较慢
Jest用法
- 安装: npm install jest-cli(需要node版本大于4)
- 配置 package.json 如下
- 运行: npm test
- 调试(使用node-debug再浏览器中调试):node-debug –nodejs –harmony ./node_modules/jest-cli/bin/jest.js –runInBand tests/getUser-test.js
- 运行单个文件 ./node_modules/jest-cli/bin/jest.js tests/getUser-test.js
1 2 3 4 5 6 7 8
| { "name": "jest-test-examples", "version": "0.0.1", "dependencies": {}, "scripts": { "test": "jest" } }
|
下面就具体介绍一下使用jest进行测试的方法,采用的例子为jest的官方实例
一个简单的测试
1 2 3 4 5 6 7
| jest.dontMock('../src/sum'); describe('sum', function() { it('adds 1 + 2 to equal 3', function() { var sum = require('../src/sum'); expect(sum(1, 2)).toBe(3); }); });
|
describe和it还有expect都使用了jasmine的语法, jest会自动mock所有的依赖模块,对模块中所有的输出方法予以遍历并进行mock,对于要测试的模块使用jest.dontMock标识,jest就不会去mock.
异步的单元测试实例
异步是javascript的灵魂, 所以异步的测试也是极其重要的,下面看看jest关于异步程序的测试,假如有这样个ajax程序,获取数据并进行解析,对其进行测试
- 1 ajax的数据获取是否正确
- 2 parseUserJson是否正确
在第二个测试用例中使用了genMockFunction,用来对回调函数进行mock,在jest中有两种方式进行函数的mock
- 1 使用xFunc = require(‘xx’);
- 2 使用xFunc = jest.genMockFunction();
使用后,会在xFunc.calls中存储有关函数的调用信息,例如
- //mock.calls.length 记录了函数被调用了几次
- //mock.calls[0][0] 被调用函数的第一个参数
- //mock.calls[0][1] 第二个参数
代码如下: $.ajax是一个被mock的函数,callback也被mock,getUser(callback)调用后,可以通过检测传递的参数判断是否正确.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var $ = require('jquery'); function parseUserJson(userJson) { return { loggedIn: true, fullName: userJson.firstName + ' ' + userJson.lastName }; } function fetchCurrentUser(callback) { return $.ajax({ type: 'GET', url: 'http://example.com/currentUser', success: function(userJson) { callback(parseUserJson(userJson)); } }); } module.exports = fetchCurrentUser;
|
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
| jest.dontMock('../src/getUser'); describe('getUser', function() { it('calls into $.ajax with the correct params', function() { var $ = require('jquery'); var getUser = require('../src/getUser'); function dummyCallback() {} getUser(dummyCallback); expect($.ajax).toBeCalledWith({ type: 'GET', url: 'http://example.com/currentUser', success: jasmine.any(Function) }); }); it('calls the callback when $.ajax requests are finished', function() { var $ = require('jquery'); var getUser = require('../src/getUser'); var callback = jest.genMockFunction(); getUser(callback); $.ajax.mock.calls[0][0].success({ firstName: 'Bobby', lastName: '");DROP TABLE Users;--' }); expect(callback.mock.calls[0][0]).toEqual({ loggedIn: true, fullName: 'Bobby ");DROP TABLE Users;--' }); }); });
|
React组件的单元测试实例
假如我们有这样一个checkbox react组件, 如下, react的es6写法请参考我的blog-ES6的核心语法与应用
原理非常简单,点击checkbox切换label的状态.我们的测试代码如下, 使用了react-addons-test-utils这个模块, 模块的renderIntoDocument用于将react组件渲染到document中,
并且支持产生模拟事件:TestUtils.Simulate.change.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React from 'react'; class Checkbox extends React.Component { constructor(props) { super(props); this.state = {isChecked: false}; this.changeState = this.changeState.bind(this); } changeState () { this.setState({isChecked: !this.state.isChecked}) } render() { return (<label> <input type="checkbox" checked={this.state.isChecked} onChange={this.changeState} /> {this.state.isChecked ? this.props.labelOn : this.props.labelOff} </label>) } } export default Checkbox;
|
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
| import React from 'react'; import TestUtils from 'react-addons-test-utils'; import ReactDom from 'react-dom'; jest.dontMock('../src/checkbox'); const Checkbox = require('../src/checkbox'); describe('test react checkbox component', () => { it('change the label after click', () => { var checkbox = TestUtils.renderIntoDocument(<Checkbox labelOn="On" labelOff="Off" />); var checkboxNode = ReactDom.findDOMNode(checkbox); //https://facebook.github.io/jest/docs/api.html#expect-value expect(checkboxNode.textContent).toEqual('Off'); TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(checkbox, 'input')); expect(checkboxNode.textContent).toEqual('On'); }); });
|
运行的时候我们需要通过babel预处理一下,通过如下的方式配置package.json即可运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| "scripts": { "test": "jest" }, "jest": { "scriptPreprocessor": "<rootDir>/node_modules/babel-jest", "unmockedModulePathPatterns": [ "<rootDir>/node_modules/react", "<rootDir>/node_modules/react-dom", "<rootDir>/node_modules/react-addons-test-utils" ], "modulePathIgnorePatterns": [ "<rootDir>/node_modules/" ] }
|
手动mock
经常我们需要模拟某个模块中的方法(此方法实现非常复杂依赖第三方的模块)用来测试另一个模块的输入输出是否正确,jest就提供非常方便的mock机制,例如,我们在A模块中依赖jquery的fx方法
而fx方法又依赖于其他方法, 因为我们只关心fx的输出,所以我们就可以直接用来模拟,方法如下:
- 建立mocks文件夹
- 新建jquery模块:jquery.js
- 使用genMockFromModule和mockImplementation API 如下
1 2 3 4 5 6 7 8 9
| var jqueryMocks = jest.genMockFromModule('jquery'); var mock_fx = function () { return 'mockValue'; }; jqueryMocks.fx.mockImplementation(mock_fx); module.exports = jqueryMocks;
|
这样就可以在测试代码中直接引用已经模拟好的fx函数进行测试了,直接对模块的输入控制,减少了依赖,实现测试的”解耦”.
1 2 3 4 5 6
| describe('jest mocks', function () { it('jquery mock getEnv value', function(){ var value = require('jquery').fx(); expect(value).toEqual('mockValue') }); });
|
代码参考
源码