基于 Jest + Enzyme 的 React 单元测试

CSDN博主
• 阅读 13903

前言

如果你想学习 React 单元测试,那就从这篇文章开始吧。Star 项目,clone 到本地,根据教程走一遍,有任何问题欢迎 issue 讨论。

项目GitHub地址:react-test-demo

文章主要内容如下:

  • Jest 和 Enzyme 的基本介绍
  • 测试环境搭建
  • 测试脚本编写

    • UI 组件测试
    • Reducer 测试
  • 运行并调试
  • 参考资料

Jest、Enzyme 介绍

Jest 是 Facebook 发布的一个开源的、基于 Jasmine 框架的 JavaScript 单元测试工具。提供了包括内置的测试环境 DOM API 支持、断言库、Mock 库等,还包含了 Spapshot Testing、 Instant Feedback 等特性。

Airbnb开源的 React 测试类库 Enzyme 提供了一套简洁强大的 API,并通过 jQuery 风格的方式进行DOM 处理,开发体验十分友好。不仅在开源社区有超高人气,同时也获得了React 官方的推荐。

测试环境搭建

在开发 React 应用的基础上(默认你用的是 Webpack + Babel 来打包构建应用),你需要安装 Jest Enzyme,以及对应的 babel-jest

npm install jest enzyme babel-jest --save-dev

下载 npm 依赖包之后,你需要在 package.json 中新增属性,配置 Jest:

  "jest": {
    "moduleFileExtensions": [
      "js",
      "jsx"
    ],
    "moduleNameMapper": {
      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
      ".*\\.(css|less|scss)$": "<rootDir>/__mocks__/styleMock.js"
    },
    "transform": {
      "^.+\\.js$": "babel-jest"
    }
  },

并新增test scripts

"scripts": {
    "dev": "NODE_ENV=development webpack-dev-server  --inline --progress --colors --port 3000 --host 0.0.0.0 ",
    "test": "jest"
  }

其中 :

  • moduleFileExtensions:代表支持加载的文件名,与 Webpack 中的 resolve.extensions 类似
  • moduleNameMapper:代表需要被 Mock 的资源名称。如果需要 Mock 静态资源(如less、scss等),则需要配置 Mock 的路径 <rootDir>/__mocks__/yourMock.js
  • transform 用于编译 ES6/ES7 语法,需配合 babel-jest 使用

上面三个是常用的配置,更多 Jest 配置见官方文档:Jest Configuration

测试脚本编写

UI 组件测试

环境搭建好了,就可以开始动手写测试脚本了。在开始之前,先分析下 Todo 应用的组成部分。

基于 Jest + Enzyme 的 React 单元测试

应用主体结构如下 src/component/App.js

class App extends Component {
  render() {
    const { params } = this.props;
    return (
      <section className="todoapp">
        <div className="main">
          <AddTodo />
          <VisibleTodoList filter={params.filter || 'all'} />
        </div>
         <Footer />
      </section>
    )
  }
}

可以发现 整个应用可以分为三个组件:

  • 最外层的 <App />
  • 中间的 Input 输入框 <AddTodo />
  • 下面的 TODO 列表 <VisibleTodoList />

其中 <App/> 是 UI 组件,<AddTodo /><VisibleTodoList /> 是智能组件,我们需要找到智能组件所对应的 UI 组件 <AddTodoView/><TodoList/>

<AddTodoView/> 就是一个 Input 输入框,接受文字输入,敲下回车键,创建一个 Todo。代码如下 src/component/AddTodoView.js

import React, { Component, PropTypes } from 'react'
class AddTodoView extends Component {
  render() {
    return (
      <header className="header">
        <h1>todos</h1>
        <input
          className="new-todo"
          type="text"
          onKeyUp={e => this.handleClick(e)}
          placeholder="input todo item"
          ref='input' />
      </header>
    )
  }

  handleClick(e) {
    if (e.keyCode === 13) {
      const node = this.refs.input;
      const text = node.value.trim();
      text && this.props.onAddClick(text);
      node.value = '';
    }
  }
}

了解了该组件的功能之后,我们首先需要明确该组件需要测试哪些点:

  • 组件是否正常渲染
  • 当用户输入内容敲下回车键时,是否能正常的调用 props 传递的 onAddClick(text) 方法
  • 创建完成后清除 Input 的值
  • 当用户没有输入任何值时,敲下回车时,应该不调用 props 传递的 onAddClick(text) 方法

经过上面的分析之后,我们就可以开始编写单元测试脚本了。

第一步:引入相关 lib
import React from 'react'
import App from '../../src/component/App'
import { shallow } from 'enzyme'

在这里我们引入了 shallow 方法,它是 Enzyme 提供的 API 之一,可以实现浅渲染。其作用是仅仅渲染至虚拟节点,不会返回真实的节点,能极大提高测试性能。但是它不适合测试包含子组件、需要测试声明周期的组件。
Enzyme 还提供了其他两个 API:

  • mount:Full Rendering,非常适用于存在于 DOM API 存在交互组件,或者需要测试组件完整的声明周期
  • render:Static Rendering,用于 将 React 组件渲染成静态的 HTML 并分析生成的 HTML 结构。render 返回的 wrapper 与其他两个 API 类似。不同的是 render 使用了第三方 HTML 解析器和 Cheerio

一般情况下,shallow 就已经足够用了,偶尔情况下会用到 mount

第二步:模拟 Props,渲染组件创建 Wrapper

这一步,我们可以创建一个 setup 函数来实现。

const setup = () => {
  // 模拟 props
  const props = {
    // Jest 提供的mock 函数
    onAddClick: jest.fn()
  }

  // 通过 enzyme 提供的 shallow(浅渲染) 创建组件
  const wrapper = shallow(<AddTodoView {...props} />)
  return {
    props,
    wrapper
  }
}

Props 中包含函数的时候,我们需要使用 Jest 提供的 mockFunction

第四步:编写 Test Case

这里的 Case 根据我们前面分析需要测试的点编写。

Case1:测试组件是否正常渲染

describe('AddTodoView', () => {
  const { wrapper, props } = setup();

  // case1
  // 通过查找存在 Input,测试组件正常渲染
  it('AddTodoView Component should be render', () => {
    //.find(selector) 是 Enzyme shallow Rendering 提供的语法, 用于查找节点
    // 详细用法见 Enzyme 文档 http://airbnb.io/enzyme/docs/api/shallow.html
    expect(wrapper.find('input').exists());
  })
})

写完第一个测试用例之后,我们可以运行看看测试的效果。在 Terminal 中输入 npm run test,效果如下:

基于 Jest + Enzyme 的 React 单元测试

Case2: 输入内容并敲下回车键,测试组件调用props的方法

  it('When the Enter key was pressed, onAddClick() shoule be called', () => {
    // mock input 输入和 Enter事件
    const mockEvent = {
      keyCode: 13, // enter 事件
      target: {
        value: 'Test'
      }
    }
    // 通过 Enzyme 提供的 simulate api 模拟 DOM 事件
    wrapper.find('input').simulate('keyup',mockEvent)
    // 判断 props.onAddClick 是否被调用
    expect(props.onAddClick).toBeCalled()
  })

上面的代码与第一个 case 多了两点:

  • 增加了 mockEvent,用于模拟 DOM 事件
  • 使用 Enzyme 提供的 .simulate(’keyup‘, mockEvent) 来模拟点击事件,这里的 keyup 会自动转换成 React 组件中的 onKeyUp 并调用。

我们再运行 npm run test 看看测试效果:

基于 Jest + Enzyme 的 React 单元测试

经过上面两个 Test Case 的分析,接下来的 Case3 和 Case4 思路也是一样,具体写法见代码: __test__/component/AddTodoView.spec.js,这里就不一一讲解了。

Reducer 测试

由于 Reducer 是纯函数,因此对 Reducer 的测试非常简单,Redux 官方文档也提供了测试的例子,代码如下:

import reducer from '../../reducers/todos'
import * as types from '../../constants/ActionTypes'

describe('todos reducer', () => {
  it('should return the initial state', () => {
    expect(
      reducer(undefined, {})
    ).toEqual([
      {
        text: 'Use Redux',
        completed: false,
        id: 0
      }
    ])
  })

  it('should handle ADD_TODO', () => {
    expect(
      reducer([], {
        type: types.ADD_TODO,
        text: 'Run the tests'
      })
    ).toEqual(
      [
        {
          text: 'Run the tests',
          completed: false,
          id: 0
        }
      ]
    )

    expect(
      reducer(
        [
          {
            text: 'Use Redux',
            completed: false,
            id: 0
          }
        ],
        {
          type: types.ADD_TODO,
          text: 'Run the tests'
        }
      )
    ).toEqual(
      [
        {
          text: 'Run the tests',
          completed: false,
          id: 1
        },
        {
          text: 'Use Redux',
          completed: false,
          id: 0
        }
      ]
    )
  })
})

更多关于 Redux 的测试可以看官网提供的例子:编写测试-Redux文档

调试及测试覆盖率报告

在运行测试脚本过程,Jest 的错误提示信息友好,通过错误信息一般都能找到问题的所在。
同时 Jest 还提供了生成测试覆盖率报告的命令,只需要添加上 --coverage 这个参数既可生成。不仅会在终端中显示:

基于 Jest + Enzyme 的 React 单元测试

而且还会在项目中生成 coverage 文件夹,非常方便。

资料

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这