【前端单元测试入门02】react的单元测试之Enzyme

React项目的单元测试

React的组件结构和JSX语法,对上一章的内容来讲进行测试显得很勉强。
React官方已经提供了一个测试工具库:react-dom/test-utils
只是用起来不够方便,于是有了一些第三方的封装库,比如Airbnb公司的Enzyme

测试项目的配置

本次测试项目是根据上一章的测试项目衍生而来,包含上一章讲到的Mocha和chai,这里只介绍新加的一些模块。
项目结构图如下:


测试项目结构图

因为是React项目,所以自然需要安装React的一些东西:

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

然后.babelrc文件内容改为

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

example.jsx的内容为:

import React from 'react'

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

example.test.js的内容暂时为空

Enzyme 的安装与配置

npm install --save-dev enzyme

而enzyme还需要根据React的版本安装适配器,适配器对应表如下:

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 的使用

现在开始用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对应的版本

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环境,因为根本没有加载进DOM。
shallow的函数输入组件,返回组件的浅渲染结果,而返回的结果可以用类似jquery的形式获取组件的信息。
运行

npm test 

也就是:

mocha --require babel-core/register

得到以下结果:


浅渲染测试运行结果

浅渲染测试与子组件的相关的代码:

现在修改我们的例子,新加一个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的个数:${buttonObj.length}`)
    console.info(`查找到span的个数:${spanObj.length}`)

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

测试结果为:


浅渲染测试子组件的结果

如上图所示,shallow所得到的浅渲染对象中差找不到子组件Sub的span元素。
为了解决上面这种情况,Enzyme给出的解决方案为用mount实现 Full DOM Rendering

Enzyme 的使用之mount

mount方法用于将React组件加载为真实DOM节点。
然而真实DOM需要一个浏览器环境,为了解决这个问题,我们可以用到jsdom.
下面是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模拟一个浏览器环境去加载真实的DOM节点。
首先安装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"
  }

这样在运行npm test时,会先用babel解析js,然后再执行setup.js中的代码,给global对象模拟一个浏览器环境。

在配置好模拟浏览器环境后,修改测试代码为:

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的个数:${buttonObj.length}`)
    console.info(`查找到span的个数:${spanObj.length}`)

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

运行

npm test

结果为:


mount的Full DOM Rendering测试结果

如上,在mount得到的结果中可以找到子组件的元素

Enzyme 的使用之render

而Enzyme还提供了一个不需要jsdom模拟环境解决子组件测试的方法:render
Enzyme的render函数得到的结果被称为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会根据react组件得到一个静态HTML文本结果,借助一个第三方的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的静态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的个数:${buttonObj.length}`)
    console.info(`查找到span的个数:${spanObj.length}`)

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

测试结果为:


render测试结果

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(), '首页')
  }
})

结果:


shallow测试结果
render测试结果
mount测试结果

结果证明:
shallow果然最快,这是肯定的,但是因为shallow的局限性,我们可能更想知道render和mount的效率。
事实证明,render的效率是mount的两倍。
有人可能要质疑你为什么不将次数弄更大一点,因为在设定为1000次的情况下mount直接超时了,也就是超过了mocha的默认执行时间限定2000ms。
那么问题来了,mount存在的价值是什么,render就可以测试子组件,render还不需要jsdom和额外的配置。
当然是有价值的,shallow和mount因为都是dom对象的缘故,所以都是可以模拟交互的,比如

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

而render是不能的。

小结

简而言之,Enzyme主要包括三个测试:
一个是浅渲染的shallow,这个生成虚DOM对象,所以渲染最快,然而它并不能测试子组件的相关代码。
另一个是DOM渲染mount,它会生成完整的DOM节点,所以可以测试子组件。但是要依赖一个用jsdom模拟的浏览器环境。
最后一个是HTML文本渲染render,它会将react组件渲染为html文本,然后在内部通过Cheerio自动生成一个Cheerio对象。

渲染方法 是否可以测试子组件 是否可以模拟交互 性能(测试500次)
shallow 116ms
mount 421ms
render 984ms

文中介绍了简单的用法,具体的API文档见:
Shallow Rendering
Full DOM Rendering
Static Rendered Markup

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345