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é)果存放在該文件夾中??煺战Y(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容