歡迎移步我的博客閱讀:《React 測(cè)試驅(qū)動(dòng)教程》
測(cè)試是開發(fā)周期中的一個(gè)重要組成部分。沒有測(cè)試的代碼被稱為:遺留代碼哼丈。對(duì)于我而言,第一次學(xué)習(xí) React 和 JavaScript 的時(shí)候,感到很有壓力累奈。如果你也是剛開始學(xué)習(xí) JS/React朝扼,并加入他們的社區(qū)丐膝,那么也可能會(huì)有相同的感覺绍赛。想到的會(huì)是:
- 我應(yīng)該用哪一個(gè)構(gòu)建工具?
- 哪一個(gè)測(cè)試框架比較好鸣个?
- 我應(yīng)該學(xué)習(xí)哪種流模式羞反?
- 我需要用到流嗎布朦?
為了解決這些煩惱,我決定寫這篇文章昼窗。經(jīng)過幾個(gè)小時(shí)的博客文章閱讀是趴,查閱 JS 開發(fā)者的源碼,還有參加 Florida 的 JSConf澄惊,終于讓我找到了自己的測(cè)試“槽”唆途。開始讓我覺得沒有經(jīng)過測(cè)試的 React 程序代碼是如此的不標(biāo)準(zhǔn)和凌亂。我想活在一個(gè)沒有這種感覺的世界掸驱,但后來想想肛搬,這是不對(duì)的。
本教程所有的代碼都可以在我的 github 倉(cāng)庫(kù)中找到毕贼。
讓我們開始吧温赔!
設(shè)置 Webpack
本教程不是一個(gè)教如何使用 webpack,所以我不會(huì)詳細(xì)說鬼癣,但重要的是要了解基本的東西陶贼。
Webpack 就像 Rails 中的 Assets Pipeline 一樣。在基礎(chǔ)層面上而言待秃,在運(yùn)行 react 應(yīng)用時(shí)拜秧,
會(huì)在處理你的代碼和服務(wù)的前后,只生成一個(gè) bundle.js
在客戶端锥余。
因?yàn)樗且粋€(gè)非常強(qiáng)大的工具腹纳,所以我們會(huì)常常用到痢掠。在開始驱犹,Webpack 的功能可能會(huì)嚇到你,
但我建議你堅(jiān)持使用下去足画,一旦你了解了其中的原理雄驹,就會(huì)覺得得心應(yīng)手。而你只需給它一個(gè)機(jī)會(huì)去表現(xiàn)淹辞。
通常我們不會(huì)喜歡那些我們不會(huì)的医舆,或是害怕的。然而象缀,一旦你克服初始不適并開始理解它蔬将,總會(huì)變得很有趣。事實(shí)上央星,這正是我對(duì)測(cè)試的感受霞怀。當(dāng)開始時(shí)討厭它,在熟悉后喜歡它 :-)
如果感興趣莉给,這里有一些資源來更多地了解關(guān)于 webpack:
- Webpack Cookbook(使用的是 Babel 5毙石,但對(duì)于學(xué)習(xí) Webpack 的基本原理而言還是很有用的)
- Webpack 初學(xué)者可以看這篇文章
- Pete Hunts 所寫的 Webpack How-to
注意:如果要持續(xù)隨本教程實(shí)驗(yàn)廉沮,建議使用 Node 版本為
v5.1.0
。當(dāng)然版本>4
的也是可以的徐矩。
首先滞时,安裝所有關(guān)于 webpack 和 babel 的依賴。Babel 是一個(gè)轉(zhuǎn)譯器滤灯,允許你在開發(fā)時(shí)使用 ES6(es2015)和 ES7 的特性坪稽,然后將這些代碼轉(zhuǎn)譯成瀏覽器可以識(shí)別的 ES5 代碼。
mkdir tdd_react
cd tdd_react
npm init # follow along with normal npm init to set up project
npm i babel-loader babel-core webpack --save-dev
npm i
是 npm install 的別名鳞骤。
接下來刽漂,讓我們?cè)O(shè)置項(xiàng)目的路徑和創(chuàng)建一個(gè) webpack.config.js
文件:
mkdir src # where all our source code will live
touch src/main.js # this will be the entry point for our webpack bundling
mkdir test # place to store all our tests
mkdir dist # this is where the bundled javascript from webpack will go
touch webpack.config.js # our webpack configuration file
初始化的 webpack config 會(huì)很小。閱讀這些注釋弟孟,理解下發(fā)生了什么:
// our webpack.config.js file located in project root
var webpack = require('webpack');
var path = require('path'); // a useful node path helper library
var config = {
entry: ['./src/main.js'], // the entry point for our app
output: {
path: path.resolve(__dirname, 'dist'), // store the bundled output in dist/bundle.js
filename: 'bundle.js' // specifying file name for our compiled assets
},
module: {
loaders: [
// telling webpack which loaders we want to use. For now just run the
// code through the babel-loader. 'babel' is an alias for babel-loader
{ test: /\.js$/, loaders: ['babel'], exclude: /node_modules/ }
]
}
}
module.exports = config;
為了讓 babel 更好地工作贝咙,我們需要定義哪個(gè) presets
是我們需要用到的。讓我們繼續(xù)拂募,并且安裝 React 和 ES6 預(yù)處理所需的東西:
npm i babel-preset-react babel-preset-es2015 --save-dev
現(xiàn)在我們有一些選項(xiàng)庭猩。在 webpack config 文件中,會(huì)告訴你哪一塊是做 bebel 預(yù)處理的:
loaders: [
{
test: /\.js$/,
loaders: ['babel'],
exclude: /node_modules/,
query: {
presets: ['react', 'es2015']
}
}
]
另外的方法是將他們存在 .babelrc
文件中陈症,這也用在我的項(xiàng)目中蔼水。將 babel 預(yù)處理存儲(chǔ)在 .babelrc
中,對(duì)于以后的開發(fā)者而言录肯,更容易去找到哪個(gè) babel 預(yù)處理是可用的趴腋。此外,當(dāng)我們將 Karma 設(shè)置到 webpack 之后论咏,因?yàn)?.babelrc
文件的存在优炬,我們就不再需要其他的預(yù)處理配置了。
# inside our project root
touch .babelrc
將下面這段粘貼到預(yù)處理文件中:
# .babelrc
{
"presets": ["react", "es2015"]
}
為了確認(rèn)它能否工作厅贪,讓我們?cè)?main.js
中加入一些 react 代碼蠢护,并看看所有的包是否正常。接著安裝 React 和 React DOM:
npm i react react-dom -S
使用
-S
是--save
的別名养涮。
創(chuàng)建第一個(gè) React 組件:
# src/main.js
import React, { Component } from 'react';
import { render } from 'react-dom';
class Root extends Component {
render() {
return <h1> Hello World </h1>;
}
}
render(<Root />, document.getElementById('root'));
聰明的讀者就會(huì)察覺我們并沒有在根部創(chuàng)建一個(gè) index.html
文件葵硕。讓我們繼續(xù),當(dāng) bundle.js
編譯后贯吓,將其放到 /dist
文件夾中:
# /dist/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
非常棒懈凹,讓我們繼續(xù)。最后悄谐,我們可以運(yùn)行 webpack介评,看看一切是否正常。如果你沒有全局安裝 webpack(npm i webpack -g
)尊沸,你也可以用 node modules 方式進(jìn)行啟動(dòng):
./node_modules/.bin/webpack
Webpack 將默認(rèn)情況下尋找一個(gè)配置名稱為 webpack.config.js
威沫。如果你高興贤惯,也可以通過不同 webpack config 作為參數(shù)傳入。
在 package.json 中創(chuàng)建一個(gè)別名棒掠,來完成構(gòu)建工作:
# package.json
... other stuff
"scripts": {
"build": "webpack"
}
接下來讓 webpack-dev-server
提升開發(fā)體驗(yàn):
npm i webpack-dev-server --save-dev
將 webpack dev server 的入口加入到 webpack.config.js
中:
... rest of config
entry: [
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:3000',
'./src/main.js'
],
... rest of config
讓 script 運(yùn)行在開發(fā)服務(wù)器上:
# package.json
... other stuff
scripts: {
"dev": "webpack-dev-server --port 3000 --devtool eval --progress --colors --hot --content-base dist",
"build": "webpack"
}
在 script 中使用了 --content-base
標(biāo)記孵构,告訴 webpack 我們想服務(wù)于 /dist
文件夾。我們還定義了 3000 端口烟很,使得更像是 Rails 開發(fā)的體驗(yàn)颈墅。
最后,在 webpack 配置文件中添加一個(gè) resolve 標(biāo)記雾袱,使進(jìn)口文件看起來更直觀恤筛。下面就是配置文件最終的樣子:
var webpack = require('webpack');
var path = require('path');
var config = {
entry: [
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:3000',
'./src/main.js'
],
resolve: {
root: [
// allows us to import modules as if /src was the root.
// so I can do: import Comment from 'components/Comment'
// instead of: import Comment from '../components/Comment' or whatever relative path would be
path.resolve(__dirname, './src')
],
// allows you to require without the .js at end of filenames
// import Component from 'component' vs. import Component from 'component.js'
extensions: ['', '.js', '.json', '.jsx']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.js?$/,
// dont run node_modules or bower_components through babel loader
exclude: /(node_modules|bower_components)/,
// babel is alias for babel-loader
// npm i babel-core babel-loader --save-dev
loader: 'babel'
}
],
}
}
module.exports = config;
為確保一切工作正常,讓我們運(yùn)行開發(fā)服務(wù)器芹橡,并且確認(rèn)我們?cè)谄聊簧峡吹?“Hello World”毒坛。
npm run dev
open http://localhost:3000
你應(yīng)該看到的是這樣的:
設(shè)置 Mocha,Chai林说,Sinon 和 Enzyme
Mocha:將用于運(yùn)行我們的測(cè)試煎殷。
Chai:是我們期待的庫(kù)。應(yīng)用非常廣泛腿箩,允許使用 RSpec 一樣的語法豪直。
Sinon:將服務(wù)于 mocks/stubs/spies.
Enzyme:將用于測(cè)試我們的 React components。AirBnB 寫的一個(gè)很漂亮的測(cè)試庫(kù)珠移。
安裝這些包:
npm i mocha chai sinon --save-dev
如果我們希望能夠使用 ES6 編寫測(cè)試弓乙,那么我們需要在運(yùn)行前對(duì)代碼進(jìn)行轉(zhuǎn)譯。那么我們需要安裝 babel-register:
npm i babel-register --save-dev
加一些 npm scripts 到 package.json
中钧惧,讓測(cè)試更簡(jiǎn)單:
# ./package.json
... rest of package.json
"scripts": {
"test": "mocha --compilers js:babel-register --recursive",
"test:watch": "npm test -- --watch",
"build": "webpack",
"dev": "webpack-dev-server --port 3000 --devtool eval --progress --colors --hot --content-base dist",
},
我們的測(cè)試腳本要運(yùn)行 mocha暇韧,并使用 babel-register
進(jìn)行轉(zhuǎn)譯,然后遞歸地查看 /test
目錄垢乙。
最終锨咙,我們需要設(shè)置 Karma,因此 npm script 會(huì)變得無效追逮,但如果不設(shè)置,它將會(huì)正常工作粹舵。npm run test:watch
將會(huì)監(jiān)視程序钮孵,并在文件發(fā)生修改時(shí)重新運(yùn)行。多么高效眼滤!
確認(rèn)它能工作巴席,創(chuàng)建一個(gè) hello world 測(cè)試 /tests/helloWorld.spec.js
:
# /test/helloWorld.spec.js
import { expect } from 'chai';
describe('hello world', () => {
it('works!', () => {
expect(true).to.be.true;
});
});
哇...看起來很像 RSpec!
如果每一個(gè)測(cè)試都要引入 expect
诅需,這將變得很麻煩漾唉,因此讓我們新建一個(gè) test_helper
文件來保存這些東西:
# /test/test_helper.js
import { expect } from 'chai';
import sinon from 'sinon';
global.expect = expect;
global.sinon = sinon;
然后把它包括到 npm 腳本的運(yùn)行套件中荧库,并通過 --require ./test/test_helper.js
來聲明:
# package.json script section
"test": "mocha --compilers js:babel-register --require ./test/test_helper.js --recursive",
我也添加了 sinon,因此它也可以全局可用≌孕蹋現(xiàn)在無論什么時(shí)候分衫,我們?cè)趯懸粋€(gè)新的測(cè)試時(shí),都不需要手動(dòng)引入 expect
和 sinon
般此。
Enzyme
現(xiàn)在我們所需的“普通”測(cè)試工具都已經(jīng)設(shè)置好了(mocha蚪战,chai,sinon)铐懊,接著讓我們安裝 Enzyme邀桑,并且開始測(cè)試 React component!
安裝這個(gè)包:
npm i enzyme react-addons-test-utils --save-dev
Enzyme 的重要文檔可以在這里找到科乎。如果有時(shí)間壁畸,我推薦閱讀 Shallow Rendering 部分。
你會(huì)問茅茂,什么是 Shallow Rendering瓤摧?
對(duì)我們來說是一種組件調(diào)用 render 方法,得到我們可以斷言的 React 元素玉吁,而無需實(shí)際安裝組件到 DOM 上照弥。更多的 React 元素請(qǐng)看這。
Enzyme 會(huì)將 shallow rendered 組件包裹進(jìn)一個(gè)特殊的 wrapper
中进副,進(jìn)而讓我們可以測(cè)試这揣。如果你用過 Rails,這看起來像是 Capybara 中的 page
對(duì)象影斑。
讓我們?yōu)橐恍┖线m的 <Root />
組件進(jìn)行 TDD 的驅(qū)動(dòng)開發(fā)给赞。
這個(gè) Root 組件會(huì)是一個(gè) container
,意味著在應(yīng)用中它可以控制 state 的處理矫户。學(xué)習(xí) React 中“智能”和“笨拙”組件之間的差異片迅,對(duì)于應(yīng)用程序體系結(jié)構(gòu)是很重要的。這篇文章很好地解釋了它們皆辽。
# /tests/containers/Root.spec.js
import React from 'react'; // required to get test to work. we can get around this later with more configuration
import { shallow } from 'enzyme'; // method from enzyme which allows us to do shallow render
import Root from '../../src/containers/Root'; // import our soon to be component
describe('(Container) Root', () => {
it('renders as a <div>', () => {
const wrapper = shallow(<Root />);
expect(wrapper.type()).to.eql('div');
});
it('has style with height 100%', () => {
const wrapper = shallow(<Root />);
const expectedStyles = {
height: '100%',
background: '#333'
}
expect(wrapper.prop('style')).to.eql(expectedStyles);
});
it('contains a header explaining the app', () => {
const wrapper = shallow(<Root />);
expect(wrapper.find('.welcome-header')).to.have.length(1);
});
});
如果我們用 npm test
運(yùn)行測(cè)試柑蛇,這會(huì)失敗。因?yàn)槲覀儧]有在適當(dāng)?shù)奈恢脛?chuàng)建一個(gè)根組件驱闷。因此我們可以這樣做:
如果在任何時(shí)候你想看到這段代碼的源代碼耻台,可以在 github 倉(cāng)庫(kù) 中找到
# /src/containers/Root.js
import React, { Component } from 'react';
const styles = {
height: '100%',
background: '#333'
}
class Root extends Component {
render() {
return (
<div style={styles}>
<h1 className='welcome-header'>Welcome to testing React!</h1>
</div>
)
}
}
export default Root;
重新運(yùn)行測(cè)試就可以了。
在我們的測(cè)試中有很多重復(fù)的東西空另,因此我們還需要回去做一些重構(gòu)盆耽。由于我們沒有給 Root
傳入任何的 props,那么我們可以 shallow render 它一次,然后就在一個(gè) wrapper 中結(jié)束了我們所有的斷言摄杂。很多時(shí)候給定一個(gè)特定的 props 后坝咐,我發(fā)現(xiàn)自己包裝的部分測(cè)試會(huì)在 “sub” describe 塊中,然后給一堆斷言也有這些 props析恢。如果你用過 RSpec墨坚,就類似于使用 “context” 塊。
describe('(Container) Root', () => {
const wrapper = shallow(<Root />);
it('renders as a <div>', () => {
expect(wrapper.type()).to.eql('div');
});
it('has style with height 100%', () => {
const expectedStyles = {
height: '100%',
background: '#333'
}
expect(wrapper.prop('style')).to.eql(expectedStyles);
});
it('contains a header explaining the app', () => {
expect(wrapper.find('.welcome-header')).to.have.length(1);
});
});
盡可能地在你的測(cè)試中使用 shallow
氮昧,但偶爾也可能不用框杜。例如,如果你要測(cè)試 React 生命周期的方法時(shí)袖肥,就需要真正地將組件安裝出來咪辱。
接下來讓我們測(cè)試一個(gè)組件的安裝和調(diào)用函數(shù),當(dāng)它安裝時(shí)椎组,我們可以得到一些暴露在 sinon
上的信息和正在使用的 spies油狂。
我們可以假裝 Root
組件有一個(gè)子組件叫 CommentList
,在安裝后將調(diào)用任意的回調(diào)寸癌。當(dāng)通過給定 props 組件安裝時(shí)专筷,函數(shù)被調(diào)用,因此我們就可以測(cè)試這個(gè)場(chǎng)景蒸苇。在組件渲染時(shí)給評(píng)論列表一些 style磷蛹,然后我們就可以知道 shallow render 是如何處理這些樣式的了。CommentList
會(huì)在一個(gè)組件文件夾的 /src/components/CommentList.js
中溪烤。因?yàn)樗惶幚頂?shù)據(jù)味咳,因此完全取決于 props,換句話說它是一個(gè)純(笨拙)組件:
import React from 'react';
// Once we set up Karma to run our tests through webpack
// we will no longer need to have these long relative paths
import CommentList from '../../src/components/CommentList';
import {
describeWithDOM,
mount,
shallow,
spyLifecycle
} from 'enzyme';
describe('(Component) CommentList', () => {
// using special describeWithDOM helper that enzyme
// provides so if other devs on my team don't have JSDom set up
// properly or are using old version of node it won't bork their test suite
//
// All of our tests that depend on mounting should go inside one of these
// special describe blocks
describeWithDOM('Lifecycle methods', () => {
it('calls componentDidMount', () => {
spyLifecyle(CommentList);
const props = {
onMount: () => {}, // an anonymous function in ES6 arrow syntax
isActive: false
}
// using destructuring to pass props down
// easily and then mounting the component
mount(<CommentList {...props} />);
// CommentList's componentDidMount should have been
// called once. spyLifecyle attaches sinon spys so we can
// make this assertion
expect(
CommentList.prototype.componentDidMount.calledOnce
).to.be.true;
});
it('calls onMount prop once it mounts', () => {
// create a spy for the onMount function
const props = { onMount: sinon.spy() };
// mount our component
mount(<CommentList {...props} />);
// expect that onMount was called
expect(props.onMount.calledOnce).to.be.true;
});
});
});
還有很多檬嘀,閱讀這些注釋可以幫助你更好地理解槽驶。看看這些實(shí)踐鸳兽,讓測(cè)試可以通過掂铐,然后再回頭看看這些測(cè)試,驗(yàn)證下你所理解的東西揍异。
# /src/components/CommentList.js
import React, { Component, PropTypes } from 'react';
const propTypes = {
onMount: PropTypes.func.isRequired,
isActive: PropTypes.bool
};
class CommentList extends Component {
componentDidMount() {
this.props.onMount();
}
render() {
return (
<ul>
<li> Comment One </li>
</ul>
)
}
}
CommentList.propTypes = propTypes;
export default CommentList;
運(yùn)行 npm test
全陨,現(xiàn)在這些套件應(yīng)該可以通過測(cè)試了。
接下來讓我們添加一些 shallow rendered 測(cè)試蒿秦,當(dāng)給定一個(gè) isActive
的 props 時(shí)烤镐,來確保我們的組件使用了適當(dāng)?shù)?CSS class。
... previous tests
it('should render as a <ul>', () => {
const props = { onMount: () => {} };
const wrapper = shallow(<CommentList {...props} />);
expect(wrapper.type()).to.eql('ul');
});
describe('when active...', () => {
const wrapper = shallow(
// just passing isActive is an alias for true
<CommentList onMount={() => {}} isActive />
)
it('should render with className active-list', () => {
expect(wrapper.prop('className')).to.eql('active-list');
});
});
describe('when inactive...', () => {
const wrapper = shallow(
<CommentList onMount={() => {}} isActive={false} />
)
it('should render with className inactive-list', () => {
expect(wrapper.prop('className')).to.eql('inactive-list');
});
});
});
讓它們通過測(cè)試:
class CommentList extends Component {
componentDidMount() {
this.props.onMount();
}
render() {
const { isActive } = this.props;
const className = isActive ? 'active-list' : 'inactive-list';
return (
<ul className={className}>
<li> Comment One </li>
</ul>
)
}
}
此時(shí)你應(yīng)該對(duì)如何測(cè)試 react 組件已經(jīng)有了一個(gè)很好的理解了棍鳖。記得去閱讀 Enzyme 文檔來獲得更多的靈感。
設(shè)置 Karma
設(shè)置 Karma 可能會(huì)有些困難。坦白講渡处,這對(duì)我而言也是一件痛苦的工作镜悉。通常,當(dāng)我開發(fā) React 應(yīng)用時(shí)医瘫,我會(huì)選擇使用已經(jīng)構(gòu)建好的 starter kit侣肄,方便省事。我非常推薦開發(fā)時(shí)用的 starter kit醇份。
使用 Karma 的價(jià)值在于快速測(cè)試重載稼锅,可以多瀏覽器測(cè)試和最重要的是 webpack 預(yù)處理。一旦我們將 Karma 設(shè)置好了僚纷,在我們運(yùn)行測(cè)試程序時(shí)矩距,不僅是只有 babel-loader
,而是整個(gè) webpack config怖竭。這為我們提供了很多便利锥债,使得我們的測(cè)試環(huán)境與開發(fā)環(huán)境相同。
讓我們開始吧...
npm i karma karma-chai karma-mocha karma-webpack --save-dev
npm i karma-sourcemap-loader karma-phantomjs-launcher --save-dev
npm i karma-spec-reporter --save-dev
npm i phantomjs --save-dev
# The polyfills arn't required but will help with browser support issues
# and are easy enough to include in our karma config that I figured why not
npm i babel-polyfill phantomjs-polyfill --save-dev
很多包痊臭,我知道哮肚。相信我完成這個(gè)是非常值得的。
對(duì)于我們的示例而言广匙,我們將使用 PhantomJS允趟。沒有別的什么原因,這我在 starter kit 中已經(jīng)用到了鸦致〕奔簦可以按照自己的喜好使用 Chrome,F(xiàn)irefox 或是 Safari蹋凝,甚至在 PhantomJS 之上鲁纠。(這是用 Karma 的一件很酷的事)
在配置 karma 之前先安裝 yargs
,它能讓你使用命令行參數(shù)來定制 Karma 的配置鳍寂。
npm i yargs -S
現(xiàn)在我們可以通過創(chuàng)建一個(gè) Karma config 文件去監(jiān)視我們的文件改含,當(dāng)文件發(fā)生修改時(shí)重新運(yùn)行并很快地保存。
Karma Config:
touch karma.config.js
// ./karma.config.js
var argv = require('yargs').argv;
var path = require('path');
module.exports = function(config) {
config.set({
// only use PhantomJS for our 'test' browser
browsers: ['PhantomJS'],
// just run once by default unless --watch flag is passed
singleRun: !argv.watch,
// which karma frameworks do we want integrated
frameworks: ['mocha', 'chai'],
// displays tests in a nice readable format
reporters: ['spec'],
// include some polyfills for babel and phantomjs
files: [
'node_modules/babel-polyfill/dist/polyfill.js',
'./node_modules/phantomjs-polyfill/bind-polyfill.js',
'./test/**/*.js' // specify files to watch for tests
],
preprocessors: {
// these files we want to be precompiled with webpack
// also run tests throug sourcemap for easier debugging
['./test/**/*.js']: ['webpack', 'sourcemap']
},
// A lot of people will reuse the same webpack config that they use
// in development for karma but remove any production plugins like UglifyJS etc.
// I chose to just re-write the config so readers can see what it needs to have
webpack: {
devtool: 'inline-source-map',
resolve: {
// allow us to import components in tests like:
// import Example from 'components/Example';
root: path.resolve(__dirname, './src'),
// allow us to avoid including extension name
extensions: ['', '.js', '.jsx'],
// required for enzyme to work properly
alias: {
'sinon': 'sinon/pkg/sinon'
}
},
module: {
// don't run babel-loader through the sinon module
noParse: [
/node_modules\/sinon\//
],
// run babel loader for our tests
loaders: [
{ test: /\.js?$/, exclude: /node_modules/, loader: 'babel' },
],
},
// required for enzyme to work properly
externals: {
'jsdom': 'window',
'cheerio': 'window',
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': 'window'
},
},
webpackMiddleware: {
noInfo: true
},
// tell karma all the plugins we're going to be using to prevent warnings
plugins: [
'karma-mocha',
'karma-chai',
'karma-webpack',
'karma-phantomjs-launcher',
'karma-spec-reporter',
'karma-sourcemap-loader'
]
});
};
閱讀所有的注釋一次或兩次有助于理解這個(gè) config 是做什么的迄汛。使用 Webpack 的一大好處是全部都是普通的 JavaScript 代碼捍壤,并且我們可以“重構(gòu)”配置文件。事實(shí)上鞍爱,這正是絕大多數(shù) starter kit 所做的鹃觉!
隨著 Karma 設(shè)置完成,為運(yùn)行測(cè)試睹逃,最后一件事就是要去更新我們的 package.json:
# package.json
"scripts" {
"test": "node_modules/.bin/karma start karma.config.js",
"test:dev": "npm run test -- --watch",
"old_test": "mocha --compilers js:babel-register --require ./test/test_helper.js --recursive",
"old_test:watch": "npm test -- --watch"
}
我建議重命名舊的測(cè)試 scripts 的前綴盗扇,用 'old_' 表示祷肯。
最終的 package.json
是這樣的:
{
"name": "react-testing-starter-kit",
"version": "0.1.0",
"description": "React starter kit with nice testing environment set up.",
"main": "src/main.js",
"directories": {
"test": "tests",
"src": "src",
"dist": "dist"
},
"dependencies": {
"react": "^0.14.6",
"react-dom": "^0.14.6",
"yargs": "^3.31.0"
},
"devDependencies": {
"babel-core": "^6.4.0",
"babel-loader": "^6.2.1",
"babel-polyfill": "^6.3.14",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-register": "^6.3.13",
"chai": "^3.4.1",
"enzyme": "^1.2.0",
"json-loader": "^0.5.4",
"karma": "^0.13.19",
"karma-chai": "^0.1.0",
"karma-mocha": "^0.2.1",
"karma-phantomjs-launcher": "^0.2.3",
"karma-sourcemap-loader": "^0.3.6",
"karma-spec-reporter": "0.0.23",
"karma-webpack": "^1.7.0",
"mocha": "^2.3.4",
"phantomjs": "^1.9.19",
"phantomjs-polyfill": "0.0.1",
"react-addons-test-utils": "^0.14.6",
"sinon": "^1.17.2",
"webpack": "^1.12.11",
"webpack-dev-server": "^1.14.1"
},
"scripts": {
"test": "node_modules/.bin/karma start karma.config.js",
"test:dev": "npm run test -- --watch",
"build": "webpack",
"dev": "webpack-dev-server --port 3000 --devtool eval --progress --colors --hot --content-base dist",
"old_test": "mocha --compilers js:babel-register --require ./test/test_helper.js --recursive",
"old_test:watch": "npm test -- --watch"
},
"repository": {
"type": "git",
"url": "tbd"
},
"author": "Spencer Dixon",
"license": "ISC"
}
在測(cè)試套件中外加 webpack 預(yù)處理,我們現(xiàn)在可以刪除那些在測(cè)試內(nèi)煩人的相對(duì)路徑聲明:
// test/containers/Root.spec.js
import React from 'react';
import { shallow } from 'enzyme';
import Root from 'containers/Root'; // new import statement
// import Root from '../../src/containers/Root'; // old import statement
// test/components/CommentList.spec.js
import React from 'react';
import CommentList from 'components/CommentList'; // new import statement
// import CommentList from '../../src/components/CommentList'; // old import statement
import {
describeWithDOM,
mount,
shallow,
spyLifecycle
} from 'enzyme';
現(xiàn)在使用這個(gè) starter kit 開發(fā)疗隶,你需要輸入以下這些命令去運(yùn)行程序:
npm run dev # note the addition of run
npm run test:dev # note the addition of run
如果還有什么不清楚的地方佑笋,可以在 github 上查看該源碼。
結(jié)論
我們已經(jīng)建立了一個(gè)堅(jiān)實(shí)的測(cè)試環(huán)境斑鼻,可以根據(jù)你的項(xiàng)目具體需求去改變和發(fā)展蒋纬。在下一次的文章中,我將花更多的時(shí)間在特殊場(chǎng)景的測(cè)試坚弱,還有如何測(cè)試 Redux蜀备,我更喜歡 flux 的實(shí)現(xiàn)。
雖然我只使用 React 開發(fā)了數(shù)月荒叶,但我已經(jīng)愛上它了碾阁。我希望本教程可以幫助你更深入地理解一些 React 測(cè)試的最佳實(shí)踐。有任何問題或評(píng)論隨時(shí)聯(lián)系我停撞。測(cè)試是我們的好朋友瓷蛙!