zWingz / my-blog-config

my-blog-config
MIT License
2 stars 0 forks source link

使用jest+enzyme测试react组件 #20

Open zWingz opened 5 years ago

zWingz commented 5 years ago

前言

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

依赖

必要依赖

按需

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是常用的

他们区别是

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大概就是几个, 按照本项目中用到的,

进行测试

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

上传测试覆盖率

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

完结

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

zWingz commented 5 years ago

test comment