React Native的單元測試Jest+Enzyme+storybook
配置
Jest配置
1. 安裝
Jest在React Native利用react-native init AwesomeProject創(chuàng)建項目的時候就會默認安裝了,這里就不多介紹了。
在你使用
create-react-app或react-native init創(chuàng)建你的 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有很多環(huán)境依賴性,如果沒有主機設備,很難模擬。所以還需要添加react-native-mock,如下
yarn add react-native-mock --dev
3. 初始化配置
做后需要配置enzyme的適配器,一個一般要根據(jù)react的版本配置,現(xiàn)在項目中用的是react@16,所以如下配置
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import 'react-native-mock/mock';
Enzyme.configure({ adapter: new Adapter() });
還需要讓此配置,在所以test之前執(zhí)行,進行如下設置
// package.json
// ...
"jest": {
// ...
"setupFiles": [
"<rootDir>/__tests__/Setup"
]
}
// ...
Storybook配置
1. 安裝
執(zhí)行下面三條指令就能完成安裝
cd my-project-directory
npm i -g @storybook/cli
getstorybook
2. 運行
npm run storybook
運行
Jest 運行
-
運行全部測試用例
npm jest -
運行單個測試用例,可以借助webstorm工具,來運行,非常方便
image
Storybook運行
-
在開發(fā)組件的時候要把storybook運行起來,并寫stories
npm run storybook
用例
Jest 常用api用法實例
| 中文 | 英文 |
|---|---|
| 匹配器 | Matchers |
| 測試異步代碼 | Asynchronous |
| 模擬器 | Mock Functions |
| 全局函數(shù) | 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,先了解下區(qū)別。
render、mount、shallow的區(qū)別
render采用的是第三方庫Cheerio的渲染,渲染結果是普通的html結構,對于snapshot使用render比較合適。
shallow和mount對組件的渲染結果不是html的dom樹,而是react樹,如果你chrome裝了react devtool插件,他的渲染結果就是react devtool tab下查看的組件結構,而render函數(shù)的結果是element tab下查看的結果。
這些只是渲染結果上的差別,更大的差別是shallow和mount的結果是個被封裝的ReactWrapper,可以進行多種操作,譬如find()、parents()、children()等選擇器進行元素查找;state()、props()進行數(shù)據(jù)查找,setState()、setprops()操作數(shù)據(jù);simulate()模擬事件觸發(fā)。
shallow只渲染當前組件,只能能對當前組件做斷言;mount會渲染當前組件以及所有子組件,對所有子組件也可以做上述操作。一般交互測試都會關心到子組件,我使用的都是mount。但是mount耗時更長,內存啥的也都占用的更多,如果沒必要操作和斷言子組件,可以使用shallow。
交互測試
主要利用simulate()接口模擬事件,實際上simulate是通過觸發(fā)事件綁定函數(shù),來模擬事件的觸發(fā)。觸發(fā)事件后,去判斷props上特定函數(shù)是否被調用,傳參是否正確;組件狀態(tài)是否發(fā)生預料之中的修改;某個dom節(jié)點是否存在是否符合期望。
官方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 -
根據(jù)組件的需要,進行一些函數(shù)的測試,如下
// 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測試主要進行,返回狀態(tài)碼(200、500、502等)的驗證,必要字段的返回,指定參數(shù)傳入指定數(shù)據(jù)返回等驗證,API測試可以在和后臺定義接口的時候就寫,等后臺接口寫好后直接跑一下測試用例就可驗證。
describe('api', () => { // ... test('/api/config', () => { expect.assertions(1); // 異步斷言數(shù)量 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); }) }); // .... });




