Enzyme
是由Airbnb
所推出的React
测试工具,它模拟了jQuery
的API
,提供了一系列简洁易用的功能,这将使得我们进行React
组件测试时非常地得心应手。
一、渲染形式
在开始介绍Enzyme
之前,我们需要先了解一下React
中的组件渲染形式以及React
官方提供的测试方法。在React
中,组件要么是Virtual DOM对象
,要么是真实DOM节点
。前者是React.Component
的实例,后者则是render
和patch
到DOM
中的实际结构。对于这两种渲染形式,React
官方提供了测试的方法:
1、Shallow Rendering
这种渲染形式会将一个组件渲染为VDOM
对象,但是只渲染第一层而不渲染所有的子组件,从而处理速度非常快。此外,由于没有渲染进DOM
,所以这种渲染形式不需要DOM
环境。它的使用示例如下:
// 被测试的组件
function TitleBlock(props) {
const { title = '' } = props
return (
<div className="titleBlock">
<h3 className="title">{title}</h3>
<div className="contents">
{props.children}
</div>
</div>
)
}
测试脚本:
// react-addon-test-units 是官方提供的测试包
import TestUtils from 'react-addon-test-units'
function shallowRender(Component, props = {}) {
const renderer = TestUtils.createRenderer()
renderer.render(<Component {...props} />)
return renderer.getRenderOutput()
}
// 测试用例
describe('Shallow Rendering', () => {
it('Title should be correctly rendered', () => {
const comp = shallowRender(TitleBlock, {
title: 'HelloWorld'
})
expect(comp.props.children[0].props.children)
.to
.equal('HelloWorld')
})
})
2、DOM Rendering
官方测试工具也提供了渲染为真实DOM
的方法,主要是通过renderIntoDocument
这个方法来提供的,示例如下:
import TestUtils from 'react-addon-test-units'
import TitleBlock from 'path/to/TitleBlock'
const comp = TestUtils.renderIntoDocument(<TitleBlock />)
这里会要求方法存在于真实的DOM
环境中,但是通过Node
跑测试时,我们怎么构造这个DOM
环境呢?一个办法就是使用jsdom
,如下:
import jsdom from 'jsdom'
if (typeof document === 'undefined') {
global.document = jsdom.jsdom(/* 这里是一段HTML文本 */`
<!doctype html>
<html>
<body></body>
</html>
`)
global.window = global.document.defaultView
global.navigator = global.window.navigator
}
因此,通过模拟了document
、window
、navigator
,我们成功构造了一个DOM
环境。此外,在React
的TestUtils
中存在了一系列用于DOM Rendering
的方法,我们需要先了解它们:
scryRenderedDOMComponentsWithClass
,找出所有匹配相应className
的节点findRenderedDOMComponentWithClass
,找出匹配相应className
的节点(只返回一个节点,若0或多个匹配会报错)scryRenderedDOMComponentsWithTag
,找出所有匹配指定标签的节点findRenderedDOMComponentWithTag
,找出所有匹配指定标签的节点(只返回一个节点,若0或多个匹配会报错)scryRenderedComponentsWithType
,找出所有符合指定类型的节点findRenderedComponentWithType
,找出所有符合指定类型的节点(只返回一个节点,若0或多个匹配会报错)findAllInRenderedTree
,遍历当前组件所有的节点,只返回那些符合条件的节点
接下来,我们就可以写测试用例了,如下:
// ... jsdom引入的代码 ...
describe('DOM Rendering', () => {
it('Title should be correctly rendered', () => {
const comp = shallowRender(TitleBlock, {
title: 'HelloWorld'
})
const titleNodes = TestUtils.scryRenderedDOMComponentsWithTag(comp, 'h3')
expect(titleNodes[0].innerText)
.to
.equal('HelloWorld')
})
})
不过,对DOM
结构的获取和处理,使用TestUtils
这一套还是太麻烦了(名字又臭又长),我们其实还可以使用findDOMNode
,这个方法接收选择器信息作为参数,并返回符合选择器的节点,用法如下:
import { findDOMNode } from 'react-dom'
// ... jsdom引入的代码 ...
describe('DOM Rendering', () => {
it('Title should be correctly rendered', () => {
const comp = shallowRender(TitleBlock, {
title: 'HelloWorld'
})
const compDOM = findDOMNode(comp)
const titleNode = compDOM.querySelector('h3')
expect(titleElems[0].innerText)
.to
.equal('HelloWorld')
})
})
二、Enzyme
以上,我们已经知道了如何用React
官方的方式测试React
组件,而现在则引出本文的重头戏——Enzyme
1、三种测试方法
Enzyme
提供三种测试方法,分别是:
shallow
render
mount
其中,shallow
方式就是对官方shallow rendering
的封装,采用Enzyme
后,我们可以改写为:
import { shallow } from 'enzyme'
import TitleBlock from 'path/to/TitleBlock'
describe('Enzyme: shallow', () => {
it('Title should be correctly rendered', () => {
const comp = shallow(<TitleBlock />)
expect(comp.find('h3').at(0).text()).to.equal('HelloWorld')
})
})
而render
方式则是将组件渲染为HTML
字符串,然后分析这个字符串并返回一个对象,它与shallow
方法很像,但是使用的是HTML解析库Cheerio
。所以返回的是Cheerio
的实例对象(而不是Enzyme
实例对象),示例如下:
import { render } from 'enzyme'
import TitleBlock from 'path/to/TitleBlock'
describe('Enzyme: shallow', () => {
it('Title should be correctly rendered', () => {
const comp = render(<TitleBlock />)
expect(comp.find('h3').at(0).text()).to.equal('HelloWorld')
})
})
最后的这个mount
,则是将组件渲染为真实的DOM
节点(并且无需引入jsdom
),示例如下:
import { mount } from 'enzyme'
import TitleBlock from 'path/to/TitleBlock'
describe('Enzyme: mount', () => {
it('Title should be correctly rendered', () => {
const comp = mount(<TitleBlock />)
expect(comp.find('h3').at(0).text()).to.equal('HelloWorld')
})
})
2、API
下面是常用的Enzyme API
:
find(selector)
,返回一个对象,该对象包含了所有符合选择器条件的子组件。不过只支持简单选择器,即:- 类选择器,如:
.name
- ID选择器,如:
#name
- 标签选择器,如:
div
- 组合选择器,如:
div.name
- 组件名引用,如:
TitleBlock
- 类选择器,如:
get(idx)
,返回指定索引位置的子DOM节点at(idx)
,返回指定索引位置的子组件first()
,返回第一个子组件last()
,返回最后一个子组件type()
,返回当前组件的类型text()
,返回当前组件的文本内容html()
,返回当前组件的HTML
文本props
,返回根组件的所有属性prop(key)
,名为${key}
的属性state([key])
,返回根组件的状态setState(nextState)
,设置根组件的状态setProps(nextProps)
,设置根组件的属性simulate(EventName)
,模拟事件