愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

Enzyme学习记录

Enzyme是由Airbnb所推出的React测试工具,它模拟了jQueryAPI,提供了一系列简洁易用的功能,这将使得我们进行React组件测试时非常地得心应手。

一、渲染形式

在开始介绍Enzyme之前,我们需要先了解一下React中的组件渲染形式以及React官方提供的测试方法。在React中,组件要么是Virtual DOM对象,要么是真实DOM节点。前者是React.Component的实例,后者则是renderpatchDOM中的实际结构。对于这两种渲染形式,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
}

因此,通过模拟了documentwindownavigator,我们成功构造了一个DOM环境。此外,在ReactTestUtils中存在了一系列用于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),模拟事件