很多教程的實例對新手並不友好,這裏的例子,都是筆者自己寫的,希望適合大家的胃口。首先,我們要構建一個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