React Native 單元測(cè)試目前網(wǎng)上的資料比較少,大多數(shù)都是點(diǎn)到為止顽馋,并沒(méi)有什么實(shí)際用處尘喝,而在Github上的開(kāi)源項(xiàng)目的單元測(cè)試也不多,提供的參考也有限压昼。所以這方面只能自己一點(diǎn)點(diǎn)摸索炕倘,這里記錄一下這幾天摸索的心得钧大,以及對(duì)項(xiàng)目進(jìn)行單元測(cè)試遇到的問(wèn)題和解決方法。
單元測(cè)試框架-Jest
Jest 是Facebook的一個(gè)專門進(jìn)行Javascript單元測(cè)試的工具罩旋,也是RN自帶的一個(gè)測(cè)試框架啊央。因此沒(méi)有理由不使用。
jest官方文檔地址:https://facebook.github.io/jest/docs/getting-started.html#content
這里可以著重看下:Testing React Native Apps瘸恼,專門為RN寫的jest測(cè)試指南劣挫,雖然不多,聊勝于無(wú)东帅。
jest測(cè)試運(yùn)行方式,在終端通過(guò)命令:npm test
運(yùn)行
下面說(shuō)下Jest測(cè)試遇到對(duì)一些問(wèn)題球拦。
項(xiàng)目使用了原生模塊的第三方庫(kù)
一些RN組件或第三方組件依賴原生API來(lái)實(shí)現(xiàn)靠闭,在這種情況下運(yùn)行npm test命令會(huì)報(bào)錯(cuò)(但是測(cè)試可能會(huì)通過(guò))。
比如在項(xiàng)目中引用了react-native-linear-gradient這個(gè)第三方庫(kù)坎炼,這個(gè)線性漸變庫(kù)有使用原生模塊愧膀,因此jest測(cè)試會(huì)有以下問(wèn)題。
解決辦法:
在index.ios.js/index.android.js文件中添加mock本地模塊谣光,比如react-native-linear-gradient
這個(gè)第三方庫(kù)檩淋,我們用jest.mock方法來(lái)模擬,消除原生模塊的影響萄金。mock方法如下:
jest.mock('react-native-linear-gradient', () => 'react-native-linear-gradient')
項(xiàng)目使用了自己編寫的原生模塊
在項(xiàng)目中使用自己編寫的原生模塊蟀悦,引用時(shí)通過(guò)NativeModules.模塊名
進(jìn)行引用.但在測(cè)試時(shí)會(huì)報(bào)以下錯(cuò)誤。
參考使用原生模塊的第三方庫(kù)解決方案氧敢,我們使用同樣的方式進(jìn)行解決日戈,其中MyPageApiFunction為原生模塊名,getMyPageInfo為原生模塊中的方法孙乖,jest.fn()為jest提供的模擬方法浙炼。
jest.mock('NativeModules', () => {
return {
MyPageApiFunction: {
getMyPageInfo: jest.fn(),
},
};
});
然而這樣還是沒(méi)有解決!進(jìn)過(guò)苦苦摸索唯袄,終于在Github的issue找到了解決辦法弯屈。
修改package.json文件,在jest節(jié)點(diǎn)中添加以下配置:
"jest": {
"preset": "react-native",
"setupFiles": ["./jest/setup.js"]
}
同時(shí)在項(xiàng)目根目錄下新建一個(gè)jest文件夾和setup.js文件,setup.js只需添加一句:
import 'react-native';
即可恋拷,此時(shí)再運(yùn)行npm test 命令就不會(huì)再遇到native module cannot be null
的錯(cuò)誤了资厉。
UI組件測(cè)試,目前所知的針對(duì)UI組件測(cè)試有兩種方式梅掠,一個(gè)快照測(cè)試(Snapshot)酌住,一個(gè)是淺渲染測(cè)試(Shallow Rendering)
快照測(cè)試Snapshot
當(dāng)想要確保您的UI不會(huì)意外更改時(shí)店归,快照測(cè)試是非常有用的工具。在RN中第一次對(duì)某個(gè)組件進(jìn)行快照測(cè)試時(shí)酪我,會(huì)在同目錄下創(chuàng)建一個(gè)snapshots文件夾消痛,并將快照結(jié)果存放在該文件夾中《伎蓿快照結(jié)果文件以xxx.js.snap命名秩伞,其內(nèi)容為某個(gè)狀態(tài)下的UI組件樹(shù)。
以下是一個(gè)快照測(cè)試?yán)樱?/p>
//這是組件 Intro.js
import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
} from 'react-native';
const styles = StyleSheet.create({
container: {
alignItems: 'center',
backgroundColor: '#F5FCFF',
flex: 1,
justifyContent: 'center',
},
instructions: {
color: '#333333',
marginBottom: 5,
textAlign: 'center',
},
welcome: {
fontSize: 20,
margin: 10,
textAlign: 'center',
},
});
export default class Intro extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
This is a React Native snapshot test.
</Text>
</View>
);
}
}
現(xiàn)在創(chuàng)建一個(gè)快照測(cè)試:
// __tests__/Intro-test.js
import 'react-native';
import React from 'react';
import Intro from '../Intro';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
test('renders correctly', () => {
const tree = renderer.create(
<Intro />
).toJSON();
expect(tree).toMatchSnapshot();
});
然后運(yùn)行npm test
命令則會(huì)在snapshots文件夾輸出快照結(jié)果欺矫。
// __tests__/__snapshots__/Intro-test.js.snap
exports[`Intro renders correctly 1`] = `
<View
style={
Object {
"alignItems": "center",
"backgroundColor": "#F5FCFF",
"flex": 1,
"justifyContent": "center",
}
}>
<Text
style={
Object {
"fontSize": 20,
"margin": 10,
"textAlign": "center",
}
}>
Welcome to React Native!
</Text>
<Text
style={
Object {
"color": "#333333",
"marginBottom": 5,
"textAlign": "center",
}
}>
This is a React Native snapshot test.
</Text>
</View>
`;
下次運(yùn)行測(cè)試時(shí)纱新,渲染的輸出將與先前創(chuàng)建的快照進(jìn)行比較∧屡浚快照應(yīng)該沿代碼更改提交脸爱。當(dāng)快照測(cè)試失敗時(shí),您需要檢查是否是非預(yù)期的更改未妹。如果是預(yù)期的更改簿废,您可以使用npm test -- -u
命令來(lái)覆蓋現(xiàn)有的快照。
淺渲染測(cè)試(Shallow Rendering)
淺渲染(Shallow Rendering)在我們針對(duì)某個(gè)上層組件進(jìn)行測(cè)試時(shí)络它,可以不用渲染它的子組件族檬,所以就不用再擔(dān)心子組件的表現(xiàn)和行為,這樣就可以只對(duì)特定組件的邏輯及其渲染輸出進(jìn)行測(cè)試了化戳。Facebook 官方提供了 react-addons-test-utils 可以讓我們使用淺渲染這個(gè)特性单料,用于測(cè)試虛擬 DOM 對(duì)象,即 React.Component 的實(shí)例点楼。但我們這里介紹的不是react-addons-test-utils扫尖,而是Enzyme,來(lái)自于活躍在 JavaScript 開(kāi)源社區(qū)的 Airbnb 公司盟步,是對(duì)官方測(cè)試工具庫(kù)( react-addons-test-utils )的封裝藏斩,它模擬了 jQuery 的 API,非常直觀并且易于使用和學(xué)習(xí)却盘,提供了一些與眾不同的接口和幾個(gè)方法來(lái)減少測(cè)試的樣板代碼狰域,方便你判斷、操縱和遍歷 React Components 的輸出黄橘,并且減少了測(cè)試代碼和實(shí)現(xiàn)代碼之間的耦合兆览。
下面是官方給出的一個(gè)簡(jiǎn)單例子:
import { shallow } from 'enzyme'
describe('Enzyme Shallow', () => {
it('App should have three <Todo /> components', () => {
const app = shallow(<App />)
expect(app.find('Todo')).to.have.length(3)
})
}
shallow 方法只會(huì)渲染出組件的第一層 DOM 結(jié)構(gòu),其嵌套的子組件不會(huì)被渲染出來(lái)塞关,從而使得渲染的效率更高抬探,單元測(cè)試的速度也會(huì)更快。
下面是天翼云項(xiàng)目針對(duì)表格組件寫的淺渲染測(cè)試?yán)樱?/p>
it('renders a MineGridItem using Enzyme without backup animation', () => {
const wrapper = shallow(
<MineGridItem
info={infos[0]}
keyIndex={0}
onPress={jest.fn()}
enableAutoBackup={false}
/>
);
const {title, image, keyIndex, enableAutoBackup} = infos[0];
expect(wrapper.contains(
<View style={styles.container}>
<Image style={[styles.icon, {justifyContent: 'flex-end'}]} source={{uri: image}}>
</Image>
<Text style={styles.title}>
{title}
</Text>
</View>
)).toBe(true);
});
MineGridItem組件如下:
<TouchableHighlight style={styles.container} onPress={this.props.onPress}>
<View style={styles.container}>
<Image style={[styles.icon, {justifyContent: 'flex-end'}]} source={{uri: imageUrl}}>
</Image>
<Text style={styles.title}>
{title}
</Text>
</View>
</TouchableHighlight>
淺渲染的常見(jiàn)用法:
使用shallow方法包裹待測(cè)試組件,在expect方法中寫出期待渲染出來(lái)對(duì)UI并將兩者進(jìn)行比較小压。
不論快照測(cè)試還是淺渲染測(cè)試都是一些坑需要注意:
- 在快照測(cè)試中线梗,如果待測(cè)試的組件帶有Touchable組件,在期待組件中需要忽略Touchable組件怠益。否則運(yùn)行會(huì)報(bào)錯(cuò)
- 快照測(cè)試和淺渲染測(cè)試中仪搔,如果組件跟動(dòng)畫相關(guān)即使用 Animate.Component這些組件,在運(yùn)行時(shí)也會(huì)報(bào)錯(cuò)
TypeError: Cannot read property 'validAttributes' of undefined
蜻牢,暫時(shí)還沒(méi)有找到解決辦法 - 訂閱原生模塊的事件的API暫時(shí)還沒(méi)有辦法模擬成功
關(guān)于快照測(cè)試和淺渲染測(cè)試烤咧,完整工程請(qǐng)查看:https://github.com/ferrannp/react-native-testing-example
關(guān)于代碼覆蓋率
有待補(bǔ)充。
關(guān)于RN的單元測(cè)試了解得還不夠深抢呆,今后還會(huì)繼續(xù)完善煮嫌。