很多教程的实例对新手并不友好,这里的例子,都是笔者自己写的,希望适合大家的胃口。首先,我们要构建一个react项目,具体方法请参考《从零搭建前端开发环境》系列。当然,如果自己已经有了一套环境,那么下面将会展示demo的业务代码,可根据自己的情况进行调整。
0、安装配置jest + enzyme
详见用jest构建react项目的测试框架——1、安装与配置,与前文不一样的是,这里会采用react@16,而且会更改一些jest.config.js里面的配置,尤其是testUrl这一项,请同学们留心。
1、业务代码
src/index.jsx,项目入口文件,在jest.config.js里面把它ignore了吧,实在没必要做什么单测。
import React from 'react';
import ReactDOM from 'react-dom';
import HelloWorld from 'components/HelloWorld';
import './index.less';
ReactDOM.render(
<HelloWorld />,
document.getElementById('app'),
);
src/index.less
#app {
// It is suggested to set min-width of the wrapper
min-width: 900px;
}
src/components/HelloWorld/index.jsx,组件功能很简单,就是点击按钮,出现“Hello World”,当然还加入了样式、图片和方法引用,尽量保证测试的全面性。里面还有一道思考题,当作一个彩蛋吧。
import React from 'react';
import { formatDate } from 'util/index';
import logo from 'assets/logo.jpg';
import './index.less';
export default class HelloWorld extends React.Component {
constructor(props) {
super(props);
this.state = {
txt: '',
};
}
onShowTxt() {
this.setState({
txt: 'Hello World',
});
}
render() {
return (
<div className="hlwd">
<img src={logo} alt="logo" />
<p className="hlwd-note">When you click the btn, the time will change!</p>
<p>Because function render will be called.Think it deeply to understand it!</p>
<h1>{formatDate(new Date())}</h1>
<button onClick={() => this.onShowTxt()}>Show Hello World</button>
<p>{this.state.txt}</p>
</div>);
}
}
src/components/HelloWorld/index.less(推荐样式采用.a-b-c的这种形式,不会改变权重,缺点就是html里写的会有点长)
.hlwd {
text-align: center;
width: 500px;
margin: 0 auto;
&-note {
font-weight: bold;
}
}
src/util/index.js,常用的工具函数,觉得有用可以拿走哦。
/**
* 获取url的query
*/
export function getQuery(param) {
const reg = new RegExp(`(^|&)${param}=([^&]*)(&|$)`);
const r = window.location.search.substr(1).match(reg);
return r != null ? decodeURIComponent(r[2]) : null;
}
/**
* 获取cookie
*/
export function getCookie(name) {
const reg = new RegExp(`(^| )${name}=([^;]*)(;|$)`);
const match = document.cookie.match(reg);
return match ? match[2] : null;
}
/**
* 将 Date 转化为指定格式的String
* 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
* 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
* 例子:
* formatDate(new Date(), "yyyy-MM-dd hh:mm:ss.S") => 2006-07-02 08:09:04.423
* formatDate(new Date(), "yyyy-M-d h:m:s.S") => 2006-7-2 8:9:4.18
*/
export function formatDate(date, format = 'yyyy-MM-dd hh:mm:ss') {
if (Object.prototype.toString.call(date) !== '[object Date]') {
return null;
}
let fmt = format;
const o = {
'M+': date.getMonth() + 1, // 月份
'd+': date.getDate(), // 日
'h+': date.getHours(), // 小时
'm+': date.getMinutes(), // 分
's+': date.getSeconds(), // 秒
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
S: date.getMilliseconds(), // 毫秒
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (`${date.getFullYear()}`).substr(4 - RegExp.$1.length));
}
let tmp;
Object.keys(o).forEach((k) => {
if (new RegExp(`(${k})`).test(fmt)) {
tmp = o[k];
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? tmp : (`00${tmp}`).substr((`${tmp}`).length));
}
});
return fmt;
}
2、js单测
像工具函数这类的纯js的单测,是比较好写的,也不用太配置,只用jest就够了。下面我们对util/index.js进行单测,主要是让大家熟悉一下语法,详见jest文档。
test/spec/util.spec.js
import { getQuery, getCookie, formatDate } from 'util/index';
describe('# getQuery', () => {
/**
* 这里就涉及到了jest.config.js里面配置的testURL,这里是不能动态修改location.href的,不信可以试试
* testURL: 'https://test.com?empty=&num=0&str=str&cstr=中文&encode=%e4%b8%ad%e6%96%87',
*/
it('empty => ""', () => {
expect(getQuery('empty')).toBe('');
});
it('num => 0', () => {
expect(getQuery('num')).toBe('0');
});
it('str => str', () => {
expect(getQuery('str')).toBe('str');
});
it('cstr => 中文', () => {
expect(getQuery('cstr')).toBe('中文');
});
it('encode => 中文', () => {
expect(getQuery('encode')).toBe('中文');
});
it('null => null', () => {
expect(getQuery('null')).toBeNull();
});
});
describe('# getCookie', () => {
/**
* 这里可以操作cookie
*/
document.cookie = 'key1=value1;';
document.cookie = 'key2=value2';
it('getCookie("key1") => "value1"', () => {
expect(getCookie('key1')).toBe('value1');
});
it('getCookie("key2") => "value2"', () => {
expect(getCookie('key2')).toBe('value2');
});
it('getCookie("null") => null', () => {
expect(getCookie('null')).toBeNull();
});
});
describe('# formatDate', () => {
const DATE_0 = new Date(0);
it('formatDate(DATE_0) => "1970-01-01 08:00:00"', () => {
expect(formatDate(DATE_0)).toBe('1970-01-01 08:00:00');
});
it('formatDate(DATE_0, "M-d h:m:s") => "1-1 8:0:0"', () => {
expect(formatDate(DATE_0, 'M-d h:m:s')).toBe('1-1 8:0:0');
});
it('formatDate("test") => null', () => {
expect(formatDate('test')).toBe(null);
});
});
运行
$ npm test
src/util/index.js是不是已经100%覆盖了?浏览器打开test/coverage/lcov-report/index.html,可以看到详情,很人性化。当然,我们还没有写react组件的测试,覆盖率可以暂时忽略。
3、React组件单测
创建src/components/HelloWorld/__tests__/index.spec.js,比较推荐把组件的单测就近放置,留心我的目录结构哦。
import React from 'react';
import { shallow } from 'enzyme';
import HelloWorld from '..';
describe('<HelloWorld />', () => {
const wrapper = shallow(<HelloWorld />);
it('Renders an img', () => {
expect(wrapper.find('img').length).toBe(1);
});
it('Before click the btn', ()=>{
expect(wrapper.find('p').at(2).text()).toBe('');
});
it('After click the btn', ()=>{
wrapper.find('button').simulate('click');
expect(wrapper.find('p').at(2).text()).toBe('Hello World');
});
});
运行
$ npm test
怎么样,都100%通过了吧,强迫症的同学们可以松一口气了。
注:其实这一步很容易出问题,正如用jest构建react项目的测试框架——1、安装与配置提到的,less、img、react版本等,都有可能报错,出了问题很正常,再领会一下前一篇文章的精神吧。
4、项目结构参考
不要被这个目录结构搞得一脸懵,请移步《从零搭建前端开发环境》系列,你也可以一步一步搭建起自己的前端工程。
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