使用jest+enzyme测试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
包括shallow
、mount
和render
, 其中shallow
和mount
是常用的
他们区别是
shallow
: 只会渲染顶级组件, 而子组件不会渲染, 渲染结果是一颗react
树, 效率最高mount
: 会渲染整个组件, 包括子组件, 如果需要深入组件内部测试, 则需要使用mount
render
: 直接选择普通的html
结构.
shallow
和mount
得到结果是一个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
等