React Native的单元测试Jest+Enzyme+storybook
配置
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配置
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": [
"<rootDir>/__tests__/Setup"
]
}
// ...
Storybook配置
1. 安装
执行下面三条指令就能完成安装
cd my-project-directory
npm i -g @storybook/cli
getstorybook
2. 运行
npm run storybook
运行
Jest 运行
-
运行全部测试用例
npm jest -
运行单个测试用例,可以借助webstorm工具,来运行,非常方便
image
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
[
.context([key\]) => Any](http://airbnb.io/enzyme/docs/api/ShallowWrapper/context.html
组件测试
-
用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('<ImageButton />', module) .add('normal', () => <ImageButton title={'确认'} imageName={img.ICON_DENE} onPress={action('点击')} /> ) .add('cancel', () => <ImageButton title={'取消'} imageName={img.ICON_CANCEL} onPress={action('点击')} /> ) ; -
效果图如下
imageimageimageimage -
根据组件的需要,进行一些函数的测试,如下
// imageButton.test.js import 'react-native' import React from 'react'; import { mount, shallow } from 'enzyme'; import ImageButton from '../../src/components/ImageButton'; test('<ImageButton/>', () => { let i = 0 const onPress = () => I++ const wrapper = shallow(<ImageButton title={'1'} imageName={null} onPress={onPress}/>); 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); }) }); // .... });




