React项目的单元测试

React的组件结构和JSX语法,对上一章的内容来讲进行测试显得很勉强。

React官方已经提供了一个测试工具库:react-dom/test-utils

只是用起来不够方便,于是有了一些第三方的封装库,比如Airbnb公司的Enzyme

测试项目的配置

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

项目结构图如下:

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

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

然后.babelrc文件内容改为

  1. {
  2. "presets": [ "es2015","react" ]
  3. }

example.jsx的内容为:

  1. import React from 'react'
  2. const Example=(props)=>{
  3. return (<div>
  4. <button>{props.text}</button>
  5. </div>)
  6. }
  7. export default Example

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

Enzyme 的安装与配置

  1. 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

所以需要安装:

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

Enzyme 的使用

现在开始用Enzyme为example.jsx编写测试代码:

  1. import {assert} from 'chai'
  2. import React from 'react'
  3. import Enzyme from 'enzyme'
  4. import Adapter from 'enzyme-adapter-react-16'
  5. import Example from '../src/example'
  6. const {shallow}=Enzyme
  7. Enzyme.configure({ adapter: new Adapter() })
  8. describe('Enzyme的浅渲染测试套件', function () {
  9. it('Example组件中按钮的名字为text的值', function () {
  10. const name='按钮名'
  11. let app = shallow(<Example text={name} />)
  12. assert.equal(app.find('button').text(),name)
  13. })
  14. })

如上面代码所示,在使用Enzyme 前需要先适配React对应的版本

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

而为了避免每个测试文件都这么写,可以再test目录下新建一个配置文件:

  1. import Enzyme from 'enzyme';
  2. import Adapter from 'enzyme-adapter-react-16';
  3. Enzyme.configure({
  4. adapter: new Adapter(),
  5. });
  6. export default Enzyme;

然后测试文件的时候只需要引用这个配置文件即可:

  1. import {assert} from 'chai'
  2. import React from 'react'
  3. import Enzyme from './config/Enzyme.config';
  4. import Example from '../src/example'
  5. const {shallow}=Enzyme
  6. describe('Enzyme的浅渲染测试套件', function () {
  7. it('Example组件中按钮的名字为text的值', function () {
  8. const name='按钮名'
  9. let app = shallow(<Example text={name} />)
  10. assert.equal(app.find('button').text(),name)
  11. })
  12. })

Enzyme 的使用之浅渲染shallow

上面的例子中就用到浅渲染shallow 。

Shallow Rendering(浅渲染)指的是,将一个组件渲染成虚拟DOM对象,但是只渲染第一层,不渲染所有子组件,所以处理速度非常快。它不需要DOM环境,因为根本没有加载进DOM。

shallow的函数输入组件,返回组件的浅渲染结果,而返回的结果可以用类似jquery的形式获取组件的信息。

运行

  1. npm test

也就是:

  1. mocha --require babel-core/register

得到以下结果:

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

现在修改我们的例子,新加一个sub.jsx:

  1. import React from 'react'
  2. const Sub=(props)=>{
  3. return (<span>{props.text}</span>)
  4. }
  5. export default Sub

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

  1. import React from 'react'
  2. import Sub from './sub'
  3. const Example=(props)=>{
  4. return (<div>
  5. <button>{props.text}</button>
  6. <Sub text={props.text} />
  7. </div>)
  8. }
  9. export default Example

使用shadow测试子组件的代码:

  1. import {assert} from 'chai'
  2. import React from 'react'
  3. import Enzyme from 'enzyme'
  4. import Adapter from 'enzyme-adapter-react-16'
  5. import Example from '../src/example'
  6. const {shallow,mount}=Enzyme
  7. Enzyme.configure({ adapter: new Adapter() })
  8. describe('Enzyme shallow的浅渲染(Shallow Rendering)中', function () {
  9. it('Example组件中按钮的名字为子组件Sub中span的值', function () {
  10. const name='按钮名'
  11. let app = shallow(<Example text={name} />)
  12. const buttonObj=app.find('button')
  13. const spanObj=app.find('span')
  14. console.info(`查找到button的个数:${buttonObj.length}`)
  15. console.info(`查找到span的个数:${spanObj.length}`)
  16. assert.equal(buttonObj.text(),spanObj.text())
  17. })
  18. })

测试结果为:

如上图所示,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:

  1. npm install --save-dev jsdom

然后在test目录下新建一个文件setup.js:

  1. import jsdom from 'jsdom';
  2. const { JSDOM } = jsdom;
  3. if (typeof document === 'undefined') {
  4. const dom=new JSDOM('<!doctype html><html><head></head><body></body></html>');
  5. global.window =dom.window;
  6. global.document = global.window.document;
  7. global.navigator = global.window.navigator;
  8. }

最后修改我们的package.json中的测试脚本为:

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

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

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

  1. import {assert} from 'chai'
  2. import React from 'react'
  3. import Enzyme from 'enzyme'
  4. import Adapter from 'enzyme-adapter-react-16'
  5. import Example from '../src/example'
  6. const {shallow,mount}=Enzyme
  7. Enzyme.configure({ adapter: new Adapter() })
  8. describe('Enzyme mount的DOM渲染(Full DOM Rendering)中', function () {
  9. it('Example组件中按钮的名字为子组件Sub中span的值', function () {
  10. const name='按钮名'
  11. let app = mount(<Example text={name} />)
  12. const buttonObj=app.find('button')
  13. const spanObj=app.find('span')
  14. console.info(`查找到button的个数:${buttonObj.length}`)
  15. console.info(`查找到span的个数:${spanObj.length}`)
  16. assert.equal(buttonObj.text(),spanObj.text())
  17. })
  18. })

运行

  1. npm test

结果为:

如上,在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得到的封装对象。

修改测试代码为:

  1. import {assert} from 'chai'
  2. import React from 'react'
  3. import Enzyme from 'enzyme'
  4. import Adapter from 'enzyme-adapter-react-16'
  5. import Example from '../src/example'
  6. const {shallow,mount,render}=Enzyme
  7. Enzyme.configure({ adapter: new Adapter() })
  8. describe('Enzyme render的静态HTML渲染(Static Rendered Markup)中', function () {
  9. it('Example组件中按钮的名字为子组件Sub中span的值', function () {
  10. const name='按钮名'
  11. let app = render(<Example text={name} />)
  12. const buttonObj=app.find('button')
  13. const spanObj=app.find('span')
  14. console.info(`查找到button的个数:${buttonObj.length}`)
  15. console.info(`查找到span的个数:${spanObj.length}`)
  16. assert.equal(buttonObj.text(),spanObj.text())
  17. })
  18. })

测试结果为:

shallow ,render和mount的效率对比

空口无凭,直接上测试代码:

  1. it('测试 shallow 500次', () => {
  2. for (let i = 0; i < 500; i++) {
  3. const nav = shallow(<Nav />)
  4. assert.equal(nav.find('a').text(), '首页')
  5. }
  6. })
  7. it('测试render500次', () => {
  8. for (let i = 0; i < 500; i++) {
  9. const nav = render(<Nav />)
  10. assert.equal(nav.find('a').text(), '首页')
  11. }
  12. })
  13. it('测试mount500次', () => {
  14. for (let i = 0; i < 500; i++) {
  15. const nav = mount(<Nav />)
  16. assert.equal(nav.find('a').text(), '首页')
  17. }
  18. })

结果:

结果证明:

shallow果然最快,这是肯定的,但是因为shallow的局限性,我们可能更想知道render和mount的效率。

事实证明,render的效率是mount的两倍。

有人可能要质疑你为什么不将次数弄更大一点,因为在设定为1000次的情况下mount直接超时了,也就是超过了mocha的默认执行时间限定2000ms。

那么问题来了,mount存在的价值是什么,render就可以测试子组件,render还不需要jsdom和额外的配置。

当然是有价值的,shallow和mount因为都是dom对象的缘故,所以都是可以模拟交互的,比如

  1. const nav = mount(<Nav />)
  2. 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

【前端单元测试入门02】react的单元测试之Enzyme的更多相关文章

  1. 【前端单元测试入门05】react的单元测试之jest

    jest jest是facebook推出的一款测试框架,集成了前面所讲的Mocha和chai,jsdom,sinon等功能. 安装 npm install --save-dev jest npm in ...

  2. 【前端单元测试入门04】Karma

    Karma 官方介绍 A simple tool that allows you to execute JavaScript code in multiple real browsers. 即一个允许 ...

  3. 【前端单元测试入门03】Sinon

    前端测试存在的问题 在讲Sinon之前,我们得先讲一下在学习了Mocha.chai以及enzyme之后,我们的前端测试还存在的一些问题. 比如前台测试需要与后台交互,获取后台数据后再根据相应数据进行测 ...

  4. 【前端单元测试入门01】Mocha与chai

    Mocha 的简介 Mocha是流行的JavaScript测试框架之一,通过它添加和运行测试,从而保证代码质量 Mocha 的安装与配置 全局安装Mocha npm install -g mocha ...

  5. Visual Studio 单元测试之二---顺序单元测试

    原文:Visual Studio 单元测试之二---顺序单元测试 此文是上一篇博文:Visual Studio 单元测试之一---普通单元测试的后续篇章.如果读者对Visual Studio的单元测试 ...

  6. VS2010单元测试入门实践教程

    单元测试的重要性这里我就不多说了,以前大家一直使用NUnit来进行单元测试,其实早在Visual Studio 2005里面,微软就已经集成了一个叫Test的专门测试插件,经过几年的发展,这个工具现在 ...

  7. 前端迷思与React.js

    前端迷思与React.js 前端技术这几年蓬勃发展, 这是当时某几个项目需要做前端技术选型时, 相关资料整理, 部分评论引用自社区. 开始吧: 目前, Web 开发技术框架选型为两种的占 80% .这 ...

  8. 单元测试之NUnit一

    NUnit 分三篇文章介绍,入门者可阅读文章,有基础者直接参考官方文档.初次写博客,望大家指点. 导航: 单元测试之NUnit一 单元测试之NUnit二 单元测试之NUnit三 NUnit是什么? N ...

  9. ASP.NET Core搭建多层网站架构【12-xUnit单元测试之集成测试】

    2020/02/01, ASP.NET Core 3.1, VS2019, xunit 2.4.1, Microsoft.AspNetCore.TestHost 3.1.1 摘要:基于ASP.NET ...

随机推荐

  1. hihoCoder 1051 : 补提交卡 枚举

    思路:预处理cnt(i)表示前i个数中有多少天需要补提交卡,枚举各个连续区间,区间[j, i]中需要补提交卡的天数是cnt(i) - cnt(j-1),判断m是否大于等于cnt(i) - cnt(j- ...

  2. JDBC批量插入优化addbatch

    // 获取要设置的Arp基准的List后,插入Arp基准表中 public boolean insertArpStandardList(List<ArpTable> list) { Con ...

  3. Object方法

    1. getClass() 返回此 Object 的运行时类. 2. hashCode() 返回该对象的哈希码值. 3. equals() 指示其他某个对象是否与此对象“相等”. 4. toStrin ...

  4. 第2章 PCI总线的桥与配置

    在PCI体系结构中,含有两类桥片,一个是HOST主桥,另一个是PCI桥.在每一个PCI设备中(包括PCI桥)都含有一个配置空间.这个配置空间由HOST主桥管理,而PCI桥可以转发来自HOST主桥的配置 ...

  5. mysql常用基础操作语法(六)--对数据排序和限制结果数量的条件查询【命令行模式】

    1.使用order by对查询的结果进行排序,asc升序,desc降序: 也可以在order by后指定多个字段名和排序方式进行多级排序: 2.使用limit限制查询结果的数量: 上图中的0,代表查询 ...

  6. Java求素数时出现错误

    Java求素数时出现错误 1.具体错误如下 No enclosing instance of type Prime is accessible. Must qualify the allocation ...

  7. JBoss启动项目报错

    具体报错如下: WARNING: -logmodule is deprecated. Please use the system property 'java.util.logging.manager ...

  8. 基于am3358的lcd输出

    /*#include<stdio.h> */ #include <unistd.h> #include <stdio.h> #include <stdlib. ...

  9. freemarker自定义标签报错(一)

    freemarker自定义标签 1.错误描述 freemarker.core.ParseException: Token manager error: freemarker.core.TokenMgr ...

  10. winhex中判断+MBR+DBR+EBR方法

    [/hide] 扇区开始描述). 用 winhex 做U盘免疫AUTO.INF 用WinHex制作无法修改的AutoRun.inf文件 在我们日常工作中,经常需要使用闪存(也称为U盘或者优盘)主要是A ...