使用jest+enzyme测试react组件

Front End React

前言

最近第一次给一个项目写一个完整的测试流程, 也算是我第一次写完整的测试. 于是记一下整个测试流程 项目地址 目前项目使用的测试框架是主流的jest+enzyme

依赖

必要依赖

  • Jest
  • enzyme
  • enzyme-adapter-react-16

按需

  • 如果使用babel,则需要babel-jest
  • 如果使用typescript, 则需要ts-jest
  • 如果需要snapshot, 则需要 enzyme-to-json

Jest 配置

起初项目使用babel进行编译,后面统一转成了ts

{
  "jest": {
    "moduleNameMapper": {
      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|scss)$": "<rootDir>/test/utils.ts"
    }, // 将静态资源匹配到utils.ts中
    "moduleFileExtensions": ["ts", "tsx", "js"],
    "setupFilesAfterEnv": "<rootDir>/test/setup.ts", // jest环境初始化
    "collectCoverageFrom": ["src/**/*.{ts,tsx}"], // 覆盖率收集
    "coverageDirectory": "./coverage/", // 覆盖率输出目录
    "collectCoverage": true,
    "transform": {
      "^.+\\.(ts|tsx)$": "ts-jest" // 如果是babel, 则为babel-jest
    },
    "testMatch": ["**/__test__/*.(ts|tsx)"],
    "snapshotSerializers": [
      "enzyme-to-json/serializer" // 用来适配 toMatchSnapshot
    ]
  }
}

如果使用babel的话, 只要将ts转成js, ts-jest转成babel-jest即可。

moduleNameMapper

用来mock一些额外module, 比如sass, jpg等等.

// /test/utils.ts
module.exports = 'test-file-stub'

setupFilesAfterEnv

The path to a module that runs some code to configure or set up the testing framework before each test.

可以用来初始化test配置, 在这里需要使用enzyme-adapter

// /test/setup.ts
import { configure } from 'enzyme'
import * as ReactSixteenAdapter from 'enzyme-adapter-react-16'

configure({ adapter: new ReactSixteenAdapter() })

collectCoverageFrom

需要测试覆盖率的文件

coverageDirectory

覆盖率输出目录

transform

A map from regular expressions to paths to transformers. A transformer is a module that provides a synchronous function for transforming source files

webpack-loader类似

testMatch

The glob patterns Jest uses to detect test files.

测试文件匹配规则, 如果跟官方不同, 则修改此值.

Enzyme 使用

官方文档

简单介绍

其实enzyme上手挺简单的, 它有三个API

包括shallowmountrender, 其中shallowmount是常用的

他们区别是

  • shallow: 只会渲染顶级组件, 而子组件不会渲染, 渲染结果是一颗react树, 效率最高
  • mount: 会渲染整个组件, 包括子组件, 如果需要深入组件内部测试, 则需要使用mount
  • render: 直接选择普通的html结构.

shallowmount得到结果是一个ReactWrapper对象, 可以进行多种操作, 包括find()prop()instance()等。

基本使用

import * as React from 'react'
import { shallow, mount } from 'enzyme'
import MyComponent from '../MyComponent'
import ChildComponent from '../ChildComponent'

describt('测试xxxxx', () => {
  it('组件state以及渲染情况', () => {
    const wrapper = shallow(<MyComponent />)
    expect(wrapper.state().msg).toEqual('test msg')
    expect(wrapper.find('#childId')).toHaveLength(1) // 测试是否包含某个`element`
    expect(wrapper.find(ChildComponent)).toHaveLength(1) // 测试是否包含某个子组件
  })
  it('触发事件', () => {
    const click = jest.fn()
    const wrapper = shallow(<MyComponent onClick={click} />)
    // 触发#triggerClickElement的click事件
    wrapper.find('#triggerClickElement').simulate('click')
    // 判断click事件是否被触发
    expect(click).toBeCalledTimes(1)
  })
  // 测试函数调用
  // 默认该函数声明方式通过class.method声明
  // class MyComponent{
  //   someMethod() {}
  // }
  it('测试函数调用', () => {
    const spy = jest.spyOn(MyComponent.prototype, 'someMethod')
    const wrapper = shallow(<MyComponent />)
    // 暂且认为组件挂载时会调用`someMethod`
    // 在此测试是否正确调用
    expect(spy).toBeCalledTimes(1)
  })
  // 但是由于react需要绑定this
  // 所以一般会这样声明
  // class MyComponent {
  //   someMethod = () => {}
  // }
  // 这时候通过babel或者typescript编译后
  // 会变成类似
  // class MyComponent{
  //   constructor() {
  //     this.someMethod = () => {}
  //   }
  // }
  // 这时候someMethod不属于MyComponent.prototype
  // 所以要改变测试方式
  it('测试函数调用', () => {
    const wrapper = shallow(<MyComponent />)
    const ins = wrapper.instance()
    const spy = jest.spyOn(ins, 'someMethod')
    wrapper.update()
    ins.forceUpdate()
    expect(spy).toBeCalledTimes(1)
  })
  it('触发特定事件, 并传递参数', () => {
    // 如果要触发特定事件, 比如mousemove, keyup等等
    // 可以通过构造自定义事件, 并且使用dispatchEvent来触发
    const wrapper = shallow(<MyComponent />)
    const element = wrapper.find('.some-element')
    const event = new MouseEvent('mousemove', {
      clientX: 100,
      clientY: 100
    })
    element.getDOMNode().dispatchEvent(event)
    expect(wrapper.state.x).toEqueal(100)
  })
})

其实enzyme常用的api大概就是几个, 按照本项目中用到的,

  • state
  • find
  • prop
  • simulate

进行测试

编写完test case后, 只要调用jest即可进行测试, 同时会输出覆盖率 如果带上--watch则可以监听文件改动并进行测试

上传测试覆盖率

目前使用Codecov来管理测试覆盖率 如果在本地上传, 则需要带上token, 如果通过travisCi, 则不需要, 直接调用codecov即可。

完结

至此, 整套jest+enzyme测试流程已经跑完. 目前看来没有用高更深的测试功能, 比如说jsdom, enzyme.render