【前端單元測試入門02】react的單元測試之Enzyme

React項目的單元測試

React的組件結(jié)構(gòu)和JSX語法隔心,對上一章的內(nèi)容來講進(jìn)行測試顯得很勉強(qiáng)匈挖。
React官方已經(jīng)提供了一個測試工具庫:react-dom/test-utils
只是用起來不夠方便币喧,于是有了一些第三方的封裝庫巴柿,比如Airbnb公司的Enzyme

測試項目的配置

本次測試項目是根據(jù)上一章的測試項目衍生而來仪吧,包含上一章講到的Mocha和chai,這里只介紹新加的一些模塊隅茎。
項目結(jié)構(gòu)圖如下:


測試項目結(jié)構(gòu)圖

因為是React項目澄峰,所以自然需要安裝React的一些東西:

npm install --save react react-dom babel-preset-react

然后.babelrc文件內(nèi)容改為

{
  "presets": [ "es2015","react" ]
}

example.jsx的內(nèi)容為:

import React from 'react'

const Example=(props)=>{
    return (<div>
            <button>{props.text}</button>
        </div>)
}
export default Example

example.test.js的內(nèi)容暫時為空

Enzyme 的安裝與配置

npm install --save-dev enzyme

而enzyme還需要根據(jù)React的版本安裝適配器,適配器對應(yīng)表如下:

Enzyme Adapter Package React semver compatibility
enzyme-adapter-react-16 ^16.0.0
enzyme-adapter-react-15 ^15.5.0
enzyme-adapter-react-15.4 15.0.0-0 - 15.4.x
enzyme-adapter-react-14 ^0.14.0
enzyme-adapter-react-13 ^0.13.0

那么因為我們安裝的React版本為^16.2.0
所以需要安裝:

npm install --save-dev enzyme-adapter-react-16

Enzyme 的使用

現(xiàn)在開始用Enzyme為example.jsx編寫測試代碼:

import {assert} from 'chai'
import React from 'react'
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Example from '../src/example'

const {shallow}=Enzyme

Enzyme.configure({ adapter: new Adapter() })

describe('Enzyme的淺渲染測試套件', function () {
  it('Example組件中按鈕的名字為text的值', function () {
    const name='按鈕名'
    let app = shallow(<Example text={name} />)
    assert.equal(app.find('button').text(),name)
  })
})

如上面代碼所示辟犀,在使用Enzyme 前需要先適配React對應(yīng)的版本

Enzyme.configure({ adapter: new Adapter() })

而為了避免每個測試文件都這么寫俏竞,可以再test目錄下新建一個配置文件:

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({
  adapter: new Adapter(),
});

export default Enzyme;

然后測試文件的時候只需要引用這個配置文件即可:

import {assert} from 'chai'
import React from 'react'
import Enzyme from './config/Enzyme.config';
import Example from '../src/example'

const {shallow}=Enzyme

describe('Enzyme的淺渲染測試套件', function () {
  it('Example組件中按鈕的名字為text的值', function () {
    const name='按鈕名'
    let app = shallow(<Example text={name} />)
    assert.equal(app.find('button').text(),name)
  })
})

Enzyme 的使用之淺渲染shallow

上面的例子中就用到淺渲染shallow 。
Shallow Rendering(淺渲染)指的是堂竟,將一個組件渲染成虛擬DOM對象胞此,但是只渲染第一層,不渲染所有子組件跃捣,所以處理速度非常快夺蛇。它不需要DOM環(huán)境疚漆,因為根本沒有加載進(jìn)DOM。
shallow的函數(shù)輸入組件刁赦,返回組件的淺渲染結(jié)果娶聘,而返回的結(jié)果可以用類似jquery的形式獲取組件的信息。
運(yùn)行

npm test 

也就是:

mocha --require babel-core/register

得到以下結(jié)果:


淺渲染測試運(yùn)行結(jié)果

淺渲染測試與子組件的相關(guān)的代碼:

現(xiàn)在修改我們的例子甚脉,新加一個sub.jsx:

import React from 'react'

const Sub=(props)=>{
    return (<span>{props.text}</span>)
}
export default Sub

在原來的example.jsx中使用Sub丸升,形成嵌套:

import React from 'react'
import Sub from './sub'

const Example=(props)=>{
    return (<div>
            <button>{props.text}</button>
            <Sub text={props.text}  />
        </div>)
}
export default Example

使用shadow測試子組件的代碼:

import {assert} from 'chai'
import React from 'react'
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Example from '../src/example'

const {shallow,mount}=Enzyme

Enzyme.configure({ adapter: new Adapter() })

describe('Enzyme shallow的淺渲染(Shallow Rendering)中', function () {
  it('Example組件中按鈕的名字為子組件Sub中span的值', function () {
    const name='按鈕名'
    let app = shallow(<Example text={name} />)
    const buttonObj=app.find('button')
    const spanObj=app.find('span')

    console.info(`查找到button的個數(shù):${buttonObj.length}`)
    console.info(`查找到span的個數(shù):${spanObj.length}`)

    assert.equal(buttonObj.text(),spanObj.text())
  })
})

測試結(jié)果為:


淺渲染測試子組件的結(jié)果

如上圖所示,shallow所得到的淺渲染對象中差找不到子組件Sub的span元素牺氨。
為了解決上面這種情況狡耻,Enzyme給出的解決方案為用mount實現(xiàn) Full DOM Rendering墩剖。

Enzyme 的使用之mount

mount方法用于將React組件加載為真實DOM節(jié)點(diǎn)。
然而真實DOM需要一個瀏覽器環(huán)境夷狰,為了解決這個問題岭皂,我們可以用到j(luò)sdom.
下面是jsdom的官方介紹:

jsdom is a pure-JavaScript implementation of many web standards, notably the WHATWG DOM and HTML Standards, for use with Node.js. In general, the goal of the project is to emulate enough of a subset of a web browser to be useful for testing and scraping real-world web applications.

也就是說我們可以用jsdom模擬一個瀏覽器環(huán)境去加載真實的DOM節(jié)點(diǎn)。
首先安裝jsdom:

npm install --save-dev jsdom

然后在test目錄下新建一個文件setup.js:

import jsdom from 'jsdom';
const { JSDOM } = jsdom;

if (typeof document === 'undefined') {
    const dom=new JSDOM('<!doctype html><html><head></head><body></body></html>');
    global.window =dom.window;
    global.document = global.window.document;
    global.navigator = global.window.navigator;
}

最后修改我們的package.json中的測試腳本為:

 "scripts": {
    "test": "mocha --require babel-core/register --require ./test/setup.js"
  }

這樣在運(yùn)行npm test時沼头,會先用babel解析js爷绘,然后再執(zhí)行setup.js中的代碼,給global對象模擬一個瀏覽器環(huán)境进倍。

在配置好模擬瀏覽器環(huán)境后土至,修改測試代碼為:

import {assert} from 'chai'
import React from 'react'
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Example from '../src/example'

const {shallow,mount}=Enzyme

Enzyme.configure({ adapter: new Adapter() })

describe('Enzyme mount的DOM渲染(Full DOM Rendering)中', function () {
  it('Example組件中按鈕的名字為子組件Sub中span的值', function () {
    const name='按鈕名'
    let app = mount(<Example text={name} />)

    const buttonObj=app.find('button')
    const spanObj=app.find('span')

    console.info(`查找到button的個數(shù):${buttonObj.length}`)
    console.info(`查找到span的個數(shù):${spanObj.length}`)

    assert.equal(buttonObj.text(),spanObj.text())
  })
})

運(yùn)行

npm test

結(jié)果為:


mount的Full DOM Rendering測試結(jié)果

如上,在mount得到的結(jié)果中可以找到子組件的元素

Enzyme 的使用之render

而Enzyme還提供了一個不需要jsdom模擬環(huán)境解決子組件測試的方法:render猾昆。
Enzyme的render函數(shù)得到的結(jié)果被稱為Static Rendered Markup陶因,以下為官方的介紹

enzyme's render function is used to render react components to static HTML and analyze the resulting HTML structure.
render returns a wrapper very similar to the other renderers in enzyme, mount and shallow; however, render uses a third party HTML parsing and traversal library Cheerio. We believe that Cheerio handles parsing and traversing HTML extremely well, and duplicating this functionality ourselves would be a disservice.

意思就是說render會根據(jù)react組件得到一個靜態(tài)HTML文本結(jié)果,借助一個第三方的HTML解析庫Cheerio去生成一個類似于mount和shallow得到的封裝對象毡庆。

修改測試代碼為:

import {assert} from 'chai'
import React from 'react'
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Example from '../src/example'

const {shallow,mount,render}=Enzyme

Enzyme.configure({ adapter: new Adapter() })

describe('Enzyme render的靜態(tài)HTML渲染(Static Rendered Markup)中', function () {
  it('Example組件中按鈕的名字為子組件Sub中span的值', function () {
    const name='按鈕名'
    let app = render(<Example text={name} />)

    const buttonObj=app.find('button')
    const spanObj=app.find('span')

    console.info(`查找到button的個數(shù):${buttonObj.length}`)
    console.info(`查找到span的個數(shù):${spanObj.length}`)

    assert.equal(buttonObj.text(),spanObj.text())
  })
})

測試結(jié)果為:


render測試結(jié)果

shallow ,render和mount的效率對比

空口無憑坑赡,直接上測試代碼:

it('測試 shallow 500次', () => {
  for (let i = 0; i < 500; i++) {
    const nav = shallow(<Nav />)
    assert.equal(nav.find('a').text(), '首頁')
  }
})

it('測試render500次', () => {
  for (let i = 0; i < 500; i++) {
    const nav = render(<Nav />)
    assert.equal(nav.find('a').text(), '首頁')
  }
})

it('測試mount500次', () => {
  for (let i = 0; i < 500; i++) {
    const nav = mount(<Nav />)
    assert.equal(nav.find('a').text(), '首頁')
  }
})

結(jié)果:


shallow測試結(jié)果
render測試結(jié)果
mount測試結(jié)果

結(jié)果證明:
shallow果然最快,這是肯定的么抗,但是因為shallow的局限性毅否,我們可能更想知道render和mount的效率。
事實證明蝇刀,render的效率是mount的兩倍螟加。
有人可能要質(zhì)疑你為什么不將次數(shù)弄更大一點(diǎn),因為在設(shè)定為1000次的情況下mount直接超時了吞琐,也就是超過了mocha的默認(rèn)執(zhí)行時間限定2000ms捆探。
那么問題來了,mount存在的價值是什么站粟,render就可以測試子組件黍图,render還不需要jsdom和額外的配置。
當(dāng)然是有價值的奴烙,shallow和mount因為都是dom對象的緣故助被,所以都是可以模擬交互的,比如

 const nav = mount(<Nav />)
 nav.find('a').simulate('click')

而render是不能的切诀。

小結(jié)

簡而言之揩环,Enzyme主要包括三個測試:
一個是淺渲染的shallow,這個生成虛DOM對象幅虑,所以渲染最快丰滑,然而它并不能測試子組件的相關(guān)代碼。
另一個是DOM渲染mount倒庵,它會生成完整的DOM節(jié)點(diǎn)褒墨,所以可以測試子組件炫刷。但是要依賴一個用jsdom模擬的瀏覽器環(huán)境。
最后一個是HTML文本渲染render貌亭,它會將react組件渲染為html文本柬唯,然后在內(nèi)部通過Cheerio自動生成一個Cheerio對象。

渲染方法 是否可以測試子組件 是否可以模擬交互 性能(測試500次)
shallow 116ms
mount 421ms
render 984ms

文中介紹了簡單的用法圃庭,具體的API文檔見:
Shallow Rendering
Full DOM Rendering
Static Rendered Markup

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锄奢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子剧腻,更是在濱河造成了極大的恐慌拘央,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件书在,死亡現(xiàn)場離奇詭異灰伟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)儒旬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門栏账,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人栈源,你說我怎么就攤上這事挡爵。” “怎么了甚垦?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵茶鹃,是天一觀的道長。 經(jīng)常有香客問我艰亮,道長闭翩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任迄埃,我火速辦了婚禮疗韵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侄非。我一直安慰自己蕉汪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布彩库。 她就那樣靜靜地躺著,像睡著了一般先蒋。 火紅的嫁衣襯著肌膚如雪骇钦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天竞漾,我揣著相機(jī)與錄音眯搭,去河邊找鬼窥翩。 笑死,一個胖子當(dāng)著我的面吹牛鳞仙,可吹牛的內(nèi)容都是我干的寇蚊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼棍好,長吁一口氣:“原來是場噩夢啊……” “哼仗岸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起借笙,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤扒怖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后业稼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盗痒,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年低散,在試婚紗的時候發(fā)現(xiàn)自己被綠了俯邓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡熔号,死狀恐怖稽鞭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情跨嘉,我是刑警寧澤川慌,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站祠乃,受9級特大地震影響梦重,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亮瓷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一琴拧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘱支,春花似錦蚓胸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至汛聚,卻和暖如春锹安,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工叹哭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留忍宋,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓风罩,卻偏偏與公主長得像糠排,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子超升,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345