0. jest与其他测试框架的比较
由于笔者刚刚接触测试,经验还不是很丰富,所以只能说一些粗浅的认识。经过1周左右的调研,得出一个结论:
jest 约等于 karma + mocha + chai
也就是说,测试、断言、覆盖率等,jest一个人就都做了。如果一个东西能解决,而且没有太大的性能问题的话,为什么不选择它呢?而且他还是facebook的亲儿子,用它来做react的单测,听起来就很配~不过据说airbnb的enzyme写react的单测,非常的方便,所以我们选择建立一个jest + enzyme的测试框架。
先附上jest配置时需要解决的问题,方便大家查阅
1、.babelrc里test环境关掉去掉modules: false
2、设置moduleNameMapper适配webpack.resolve.alias
3、设置transform转换css等样式文件
4、注意enzyme与react版本匹配问题
安装jest也很简单,通常我们都会用到babel,所以还会需要babel-jest
$ npm i -D jest babel-jest
后面还会安装enzyme来测试react组件,由于需要匹配react版本,所以后面再细说。
1. jest的语法
import { sum } from '../../src/util';
describe('# sum test', () => {
it('1 + 1 = 2', () => {
expect(sum([1, 2])).toBe(3);
});
it('2 + 2 = 4', () => {
expect(sum([2, 2])).toBe(4);
});
});
不用多说什么了,跟其他框架没有什么太大区别,import要测试的模块,describe分块,it分条件,expect运行。具体语法细节参考jest官方文档。在测试普通js模块时,基本不会碰到什么问题,但是在测试react组件时,就会出现各种各样的配置问题,尤其是与webpack搭配的时候。
2. jest的配置
jest的配置文件为config目录下的 jest.config.js(也可以起其他名字或者直接写在package.json里),用jest --config config/jest.config.js 来指定运行,先附上配置文件,然后再详细说明配置时会遇到的问题。
const path = require('path');
module.exports = {
rootDir: path.resolve(__dirname, '../'),
collectCoverage: true, // 是否收集测试时的覆盖率信息
collectCoverageFrom: ['<rootDir>/src/**/*.{js,jsx,mjs}'], // 哪些文件需要收集覆盖率信息
coverageDirectory: '<rootDir>/test/coverage', // 输出覆盖信息文件的目录
coveragePathIgnorePatterns: ['/node_modules/', '<rootDir>/src/index.jsx'], // 统计覆盖信息时需要忽略的文件
moduleNameMapper: { // 主要用于与webpack的resolve.alias匹配,注意正则写法
'^src(.*)$': '<rootDir>/src$1',
'^util(.*)$': '<rootDir>/src/util$1',
'^assets(.*)$': '<rootDir>/src/assets$1',
'^components(.*)$': '<rootDir>/src/components$1',
},
setupFiles: ['<rootDir>/test/setup.js'], // 运行测试前可运行的脚本,比如注册enzyme的兼容
testMatch: [ // 匹配的测试文件
'<rootDir>/test/**/?(*.)(spec|test).{js,jsx,mjs}',
'<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}',
],
testURL: 'https://test.com?empty=&num=0&str=str&cstr=中文&encode=%e4%b8%ad%e6%96%87', // 运行环境下的url,默认about:blank
transform: {
'^.+\\.(js|jsx|mjs)$': '<rootDir>/node_modules/babel-jest',
'^.+\\.(css|less)$': '<rootDir>/test/cssTransform.js',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/test/fileTransform.js',
},
transformIgnorePatterns: [ // 转换时需要忽略的文件
'[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$',
],
};
另外还要在.babelrc里面配置一下,请根据自己项目情况调整
{
"presets": [
["env", {"modules": false }],
"react",
"stage-2"
],
"env": {
"test": {
"presets": [["env"], "react", "stage-2"]
}
}
}
主要是在env.test里面要把modules:false关掉,jest在运行单测时会默认走env.test里面的配置。
1、webpack的resolve.alias引起的问题
如果运行时出现了类似如下的提示:
$ Cannot find module '@src/util' from 'index.jsx'
基本上就是在webpack的resolve.alias设置了@src的别名,而jest找不到。这个时候就需要配置moduleNameMapper了(见上文),配置的时候要注意正则的语法,还是有点难度的。
2、css等样式引起的问题
如果运行时出现了类似如下的提示:
$ SyntaxError: Unexpected token .
基本上是因为解析css等样式问题出得问题,我们单测的时候其实不会去关心样式的,所以我们需要把所有的样式文件重定向一下,返回一个空的模块,这样就不会报错了。
这个空文件可以写成下面这样,是从create-react-app里借鉴过来的,然后配置transform属性(见上文)即可,注意文件路径,请根据自己的项目进行修改。新建test/cssTransform.js
module.exports = {
process() {
return 'module.exports = {};';
},
getCacheKey() {
// The output is always the same.
return 'cssTransform';
},
};
除了样式,我们还需要忽略图片、字体等文件,就需要新建test/fileTransform.js文件。
const path = require('path');
// This is a custom Jest transformer turning file imports into filenames.
// http://facebook.github.io/jest/docs/en/webpack.html
module.exports = {
process(src, filename) {
return `module.exports = ${JSON.stringify(path.basename(filename))};`;
},
};
3、window.location相关的问题
在测试中,我们的被测试代码有可能会访问window.location等url相关的参数,而node环境里是没有url的,这个时候就需要配置一下testURL属性(见上文),来给jest(实际是jsdom)一个默认的url属性,让测试文件去访问。这个配置暂时能够解决一些简单的问题,下面的文章会用到,但是目前还没有发现能够在单测文件里动态修改url的办法,请大牛多多指教。
3. enzyme的配置
需要根据项目的react版本来安装对应的enzyme,详见文档,下面以[email protected]为例,需要安装。react@16就不需要安装这么多,所以要根据安装时候的提示进行针对性的安装。
$ npm i -D enzyme enzyme-adapter-react-15 [email protected] [email protected]
然后需要对enzyme进行一下配置
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-15';
Enzyme.configure({ adapter: new Adapter() });
可以把这段代码保存为一个文件,如test/setup.js,然后在jest的配置文件里设置setupFiles属性(见上文),这样就不需要在每个react组件的测试文件里都重复写这段代码了。
用enzyme写测试,就像写jquery一样,对于前端同学应该没什么难度。只要把组件包裹在shallow、mount或render里面即可。至于这三者的区别,可详见官方文档,下面是个demo,还是比较容易看懂的。另外,我们会另起一篇来专门介绍如何写组件的用例。
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import AdjustRule from '../index';
describe('# Component AdjustRule', () => {
it('should render without throwing an error', () => {
expect(shallow(<AdjustRule />).contains(<p className="ajrl-title"></p>)).toBe(true);
});
it('should mount in a full DOM', () => {
expect(mount(<AdjustRule />).find('.ajrl-title').length).toBe(1);
});
it('should render to static HTML', () => {
expect(render(<AdjustRule />).find('.ajrl-title').text()).toEqual('test');
});
});
4. 结束
实践才能出真知,请看本系列的下一篇文章来上手实践。不过你得首先有个react的开发环境,可参考《从零搭建前端开发环境》系列来搭建一个自己熟悉的react开发环境。本文的配置均基于如下目录结构,请根据自身情况调整,注意领会精神。
demo
|- config/
|- jest.config.js
|- webpack.base.js
|- webpack.dev.js
|- webpack.prod.js
|- src/
|- assets/
|- logo.jpg
|- components/
|- HelloWorld/
|- __tests__/
|- index.spec.js
|- index.jsx
|- index.less
|- util/
|- index.js
|- index.jsx
|- index.less
|- test/
|- spec/
|- util.spec.js
|- .eslintrc.js
|- cssTransform.js
|- fileTransform.js
|- setup.js
|- .babelrc
|- .eslintignore
|- .eslintrc.js
|- .gitignore
|- .postcssrc.js
|- index.html
|- package.json
扩展阅读: