React Native的单元测试Jest+Enzyme+storybook
demo
配置
Jest配置
Jest
1. 安装
Jest在React Native利用react-native init AwesomeProject
创建项目的时候就会默认安装了,这里就不多介绍了。
在你使用
create-react-app
或react-native init
创建你的 React 或 React Native 项目时,Jest 都已经被配置好并可以使用了。在__tests__
文件夹下放置你的测试用例,或者使用.spec.js
或.test.js
后缀给它们命名。不管你选哪一种方式,Jest 都能找到并且运行它们。
Enzyme配置
Enzyme
1. 安装
yarn add enzyme enzyme-adapter-react-16 --dev
每个适配器可能还有其他的对等体依赖关系,您也需要安装它们。举例来说,
enzyme-adapter-react-16
对应用同版本的依赖react@16
,react-dom@16
和react-test-renderer@16
。
2. 初始化配置
由于React Native有很多环境依赖性,如果没有主机设备,很难模拟。所以还需要添加react-native-mock,如下
yarn add react-native-mock --dev
3. 初始化配置
做后需要配置enzyme的适配器,一个一般要根据react的版本配置,现在项目中用的是react@16,所以如下配置
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import 'react-native-mock/mock';
Enzyme.configure({ adapter: new Adapter() });
还需要让此配置,在所以test之前执行,进行如下设置
// package.json
// ...
"jest": {
// ...
"setupFiles": [
"/__tests__/Setup"
]
}
// ...
Storybook配置
Storybook
1. 安装
执行下面三条指令就能完成安装
cd my-project-directory
npm i -g @storybook/cli
getstorybook
2. 运行
npm run storybook
运行
Jest 运行
-
运行全部测试用例
npm jest
-
运行单个测试用例,可以借助webstorm工具,来运行,非常方便
Storybook运行
-
在开发组件的时候要把storybook运行起来,并写stories
npm run storybook
用例
Jest 常用api用法实例
中文 | 英文 |
---|---|
匹配器 | Matchers |
测试异步代码 | Asynchronous |
模拟器 | Mock Functions |
全局函数 | Global Functions |
API集合
全局方法
afterAll(fn, timeout)
afterEach(fn, timeout)
beforeAll(fn, timeout)
beforeEach(fn, timeout)
describe(name, fn)
describe.each(table)(name, fn)
describe.only(name, fn)
describe.only.each(table)(name, fn)
describe.skip(name, fn)
describe.skip.each(table)(name, fn)
require.requireActual(moduleName)
require.requireMock(moduleName)
test(name, fn, timeout)
test.each(table)(name, fn)
test.only(name, fn, timeout)
test.only.each(table)(name, fn)
test.skip(name, fn)
test.skip.each(table)(name, fn)
匹配器
expect(value)
expect.extend(matchers)
expect.anything()
expect.any(constructor)
expect.arrayContaining(array)
expect.assertions(number)
expect.hasAssertions()
expect.not.arrayContaining(array)
expect.not.objectContaining(object)
expect.not.stringContaining(string)
expect.not.stringMatching(string | regexp)
expect.objectContaining(object)
expect.stringContaining(string)
expect.stringMatching(string | regexp)
expect.addSnapshotSerializer(serializer)
.not
.resolves
.rejects
.toBe(value)
.toHaveBeenCalled()
.toHaveBeenCalledTimes(number)
.toHaveBeenCalledWith(arg1, arg2, ...)
.toHaveBeenLastCalledWith(arg1, arg2, ...)
.toHaveBeenNthCalledWith(nthCall, arg1, arg2, ....)
.toHaveReturned()
.toHaveReturnedTimes(number)
.toHaveReturnedWith(value)
.toHaveLastReturnedWith(value)
.toHaveNthReturnedWith(nthCall, value)
.toBeCloseTo(number, numDigits)
.toBeDefined()
.toBeFalsy()
.toBeGreaterThan(number)
.toBeGreaterThanOrEqual(number)
.toBeLessThan(number)
.toBeLessThanOrEqual(number)
.toBeInstanceOf(Class)
.toBeNull()
.toBeTruthy()
.toBeUndefined()
.toContain(item)
.toContainEqual(item)
.toEqual(value)
.toHaveLength(number)
.toMatch(regexpOrString)
.toMatchObject(object)
.toHaveProperty(keyPath, value)
.toMatchSnapshot(propertyMatchers, snapshotName)
.toStrictEqual(value)
.toThrow(error)
.toThrowErrorMatchingSnapshot()
Enzyme 常用api用法实例
enzyme有3种渲染方式:render
、mount
、shallow
,先了解下区别。
render
、mount
、shallow
的区别
render采用的是第三方库Cheerio
的渲染,渲染结果是普通的html结构,对于snapshot使用render
比较合适。
shallow和mount对组件的渲染结果不是html的dom树,而是react树,如果你chrome装了react devtool插件,他的渲染结果就是react devtool tab下查看的组件结构,而render
函数的结果是element tab下查看的结果。
这些只是渲染结果上的差别,更大的差别是shallow和mount的结果是个被封装的ReactWrapper
,可以进行多种操作,譬如find()、parents()、children()
等选择器进行元素查找;state()、props()
进行数据查找,setState()、setprops()
操作数据;simulate()
模拟事件触发。
shallow只渲染当前组件,只能能对当前组件做断言;mount会渲染当前组件以及所有子组件,对所有子组件也可以做上述操作。一般交互测试都会关心到子组件,我使用的都是mount
。但是mount耗时更长,内存啥的也都占用的更多,如果没必要操作和断言子组件,可以使用shallow。
交互测试
主要利用simulate()
接口模拟事件,实际上simulate是通过触发事件绑定函数,来模拟事件的触发。触发事件后,去判断props上特定函数是否被调用,传参是否正确;组件状态是否发生预料之中的修改;某个dom节点是否存在是否符合期望。
官方api
.find(selector) => ShallowWrapper
.findWhere(predicate) => ShallowWrapper
.filter(selector) => ShallowWrapper
.filterWhere(predicate) => ShallowWrapper
.hostNodes() => ShallowWrapper
.contains(nodeOrNodes) => Boolean
.containsMatchingElement(node) => Boolean
.containsAllMatchingElements(nodes) => Boolean
.containsAnyMatchingElements(nodes) => Boolean
.equals(node) => Boolean
.matchesElement(node) => Boolean
.hasClass(className) => Boolean
.is(selector) => Boolean
.exists() => Boolean
.isEmpty() => Boolean
.isEmptyRender() => Boolean
.not(selector) => ShallowWrapper
.children() => ShallowWrapper
.childAt(index) => ShallowWrapper
.parents() => ShallowWrapper
.parent() => ShallowWrapper
.closest(selector) => ShallowWrapper
.shallow([options\]) => ShallowWrapper
.render() => CheerioWrapper
.unmount() => ShallowWrapper
.text() => String
.html() => String
.get(index) => ReactElement
.getNode() => ReactElement
.getNodes() => Array
.at(index) => ShallowWrapper
.first() => ShallowWrapper
.last() => ShallowWrapper
.state([key\]) => Any
[
.context([key\]) => Any
](http://airbnb.io/enzyme/docs/api/ShallowWrapper/context.html.props() => Object
.prop(key) => Any
.key() => String
.simulate(event[, data\]) => ShallowWrapper
.setState(nextState) => ShallowWrapper
.setProps(nextProps) => ShallowWrapper
.setContext(context) => ShallowWrapper
.instance() => ReactComponent
.update() => ShallowWrapper
.debug() => String
.type() => String|Function
.name() => String
.forEach(fn) => ShallowWrapper
.map(fn) => Array
.reduce(fn[, initialValue\]) => Any
.reduceRight(fn[, initialValue\]) => Any
.slice([begin[, end\]]) => ShallowWrapper
.tap(intercepter) => Self
.some(selector) => Boolean
.someWhere(predicate) => Boolean
.every(selector) => Boolean
.everyWhere(predicate) => Boolean
.dive([options]) => ShallowWrapper
组件测试
-
用storybook做组件测试,既可以存储组件快照,也可以快速查看组件样式
例如:
// import React from 'react'; import { storiesOf } from '@storybook/react-native'; import { action } from '@storybook/addon-actions'; import { linkTo } from '@storybook/addon-links'; import * as img from './img'; import ImageButton from './ImageButton'; storiesOf('
', module) .add('normal', () => ) .add('cancel', () => ) ; -
效果图如下
-
根据组件的需要,进行一些函数的测试,如下
// imageButton.test.js import 'react-native' import React from 'react'; import { mount, shallow } from 'enzyme'; import ImageButton from '../../src/components/ImageButton'; test('
', () => { let i = 0 const onPress = () => I++ const wrapper = shallow( ); expect(wrapper.instance().props.title).toBe('1'); // uses the right handler expect(wrapper.prop('onPress')).toBe(onPress) expect(i).toBe(0); wrapper.simulate('press'); expect(i).toBe(1); });
API测试
-
API测试主要进行,返回状态码(200、500、502等)的验证,必要字段的返回,指定参数传入指定数据返回等验证,API测试可以在和后台定义接口的时候就写,等后台接口写好后直接跑一下测试用例就可验证。
describe('api', () => { // ... test('/api/config', () => { expect.assertions(1); // 异步断言数量 const param = Object.assign(defaultParam, {}); const path = '/api/config'; return api.post(path, param) .then(response => response.data) .then(response => { expect(response.errorCode).toBe(200); }) }); // .... });
参考文档
Engine单元测试调研文档