React Native單元測(cè)試實(shí)踐

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)題。

enter description here
enter description here

解決辦法:
在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ò)誤。

enter description here
enter description here

參考使用原生模塊的第三方庫(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è)試都是一些坑需要注意:

  1. 在快照測(cè)試中线梗,如果待測(cè)試的組件帶有Touchable組件,在期待組件中需要忽略Touchable組件怠益。否則運(yùn)行會(huì)報(bào)錯(cuò)
  2. 快照測(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)有找到解決辦法
  3. 訂閱原生模塊的事件的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ù)完善煮嫌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市抱虐,隨后出現(xiàn)的幾起案子昌阿,更是在濱河造成了極大的恐慌,老刑警劉巖恳邀,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宝泵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡轩娶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門框往,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鳄抒,“玉大人,你說(shuō)我怎么就攤上這事椰弊⌒斫Γ” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵秉版,是天一觀的道長(zhǎng)贤重。 經(jīng)常有香客問(wèn)我,道長(zhǎng)清焕,這世上最難降的妖魔是什么并蝗? 我笑而不...
    開(kāi)封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮秸妥,結(jié)果婚禮上滚停,老公的妹妹穿的比我還像新娘。我一直安慰自己粥惧,他們只是感情好键畴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著突雪,像睡著了一般起惕。 火紅的嫁衣襯著肌膚如雪涡贱。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天惹想,我揣著相機(jī)與錄音问词,去河邊找鬼。 笑死勺馆,一個(gè)胖子當(dāng)著我的面吹牛戏售,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播草穆,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼灌灾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了悲柱?” 一聲冷哼從身側(cè)響起锋喜,我...
    開(kāi)封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎豌鸡,沒(méi)想到半個(gè)月后嘿般,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涯冠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年炉奴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛇更。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞻赶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出派任,到底是詐尸還是另有隱情砸逊,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布掌逛,位于F島的核電站师逸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏豆混。R本人自食惡果不足惜篓像,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望崖叫。 院中可真熱鬧遗淳,春花似錦、人聲如沸心傀。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至养叛,卻和暖如春种呐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背弃甥。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工爽室, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人淆攻。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓阔墩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親瓶珊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子啸箫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容