在網上找了好久針對react-native的測試方法,但是沒有找到靠譜的方式。要么很淺只是跑了一下官方的例子,要么就是版本有點老舊,照着無法進行。jest提供的react-native例子很少,而enzyme提供的react-native-mock庫也是各種報錯,讓人很是絕望。於是乎在搜索到的信息指引下,經過自己的嘗試,我總結出了下面的測試方法,但是不是很滿意,如果哪位大神有更好的方式,懇請留個言指明一下方向。
react官方內置了jest作為單元測試的工具,再搭配上enzyme就能完美地開展測試工作。但是當測試的對象變成react-native時,測試工作就變得異常艱難。艱難的地方主要有以下幾點:
1、原生組件干擾。
2、使用enzyme去渲染react-native組件時,async/await解析存在問題,我預計是報錯了,並且錯誤被吞吃掉了,這就導致react-native組件的渲染結果不值得信任,因為當你在生命周期函數上使用了async等時,基本可以確認它的執行存在問題。
3、組件的文件調用了其他的方法文件時,並且這些方法是異步操作,包含了數據請求操作等,如何去mock數據並且替換調用。
4、Image組件使用require獲取文件報錯。
基於以上幾點問題,我們開始一一解決。
1、官方提供了配置可以模擬原生組件,當有第三方組件時,可以通過jest.mock方法做模擬。
假定引入了一個第三方組件叫做react-native-go,並且上面有一個方法叫to,然后你在代碼中調用了這個方法,那么你就可以這樣寫一個假的對象來返回對應方法,具體實現如下
jest.mock('react-native-go', ()=>{
return {
to: ()=>{}
}
});
2、既然內部調用async有問題,那么我們可不可以直接在外部調用內部方法呢?當然你直接用shallow返回的組件對象去調用內部方法是不行的,這時候只會報錯,說你調用的對象不是函數。那么怎么得到this指針呢?組件在初始化時會執行各個生命周期函數,而我們可以通過props傳遞函數給組件來調用,這就有解決無法獲取this的問題的途徑了。接着我們就可以通過閉包函數和修改函數指針的方式來把this暴露到測試環境中。
3、組件內部引用了外部文件。這個也簡單了,基於第二點的辦法,我們就可以造一個測試專用的方法對象來替換掉實際使用的工具對象,通過props和生命周期函數直接覆蓋掉,這樣再加上mockjs便可以模擬數據。
4、
參照官方文檔中Image的另一個調用方式是uri,並且可以直接傳入base64文件,那么我們就可以把require去掉,改成直接引用base64文件。而圖片轉換base64,我們可以通過nodejs來實現。
通過上面這些方式,我們就可以開始react-native的測試工作了。
謝謝
walkOnly提供的解決方法。
圖片引入的正確解決方式是通過package.json中jest的配置來解決的。可以使用transform來將其替換成assetFileTransformer.js。
這是assetFileTransformer.js的源碼
'use strict';
/* eslint-env node */
const path = require('path');
const createCacheKeyFunction = require('fbjs-scripts/jest/createCacheKeyFunction');
module.exports = {
// Mocks asset requires to return the filename. Makes it possible to test that
// the correct images are loaded for components. Essentially
// require('img1.png') becomes `Object { "testUri": 'path/to/img1.png' }` in
// the Jest snapshot.
process: (_, filename) =>
`module.exports = {
testUri: ${JSON.stringify(path.relative(__dirname, filename))}
};`,
getCacheKey: createCacheKeyFunction([__filename]),
};
具體代碼如下:
首先,按照配置jest測試環境。
1、安裝依賴包
"devDependencies": {
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"jest": "^21.2.1",
"react-dom": "^16.2.0"
},
2、編寫.babelrc
{
"presets": ["react-native"]
}
3、jest配置
"scripts": {
"test": "jest"
},
"jest": {
"preset": "react-native",
"transform": {
"\\.(jpg|jpeg|png|webp|gif|svg|wav|mp3|mp4|m4a|aac)$": "<rootDir>/node_modules/react-native/jest/assetFileTransformer.js"
}
}
先寫一個我要測試的組件
import React, {Component} from 'react';
import {
View
} from 'react-native';
//工具方法包含獲取數據請求send
let Core = require('./core');
export default class AboutUsPage extends Component<{}>{
constructor(props){
super(props);
if(typeof this.props.getThis === 'function'){
this.props.getThis.call(this);
if (this.props.testCore) {
Core = this.props.testCore;
}
}
}
async componentWillMount(){
this.setState({
name: await this.firstStep()
})
}
async firstStep(){
return await this.secondStep();
}
async secondStep(){
return await Core.send('/getData');
}
render(){
return (
<View></View>
)
}
}
core文件
let Core = {
async send() {//請求異步數據,返回promise
...
}
};
module.exports = Core;
testCore文件,暴露兩個函數,一個send用以調用數據,一個setSendData用以設置要返回的數據
"use strict";
let caches = {
};
let currentRequest = {};
let Core = {
async setSendData(key, status, data) {
caches[key] = {
status,
data
};
},
async send(key){
let res = caches[key];
if(res.status){
return Promise.resolve(res.data);
}else{
return Promise.reject(res.data);
}
}
};
module.exports = Core;
test.js測試文件
'use strict';
import React from 'react';
import AboutUsPage from './AboutUsPage';
import {configure, shallow} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import testCore from './test.core';
configure({ adapter: new Adapter() });
testCore.setSendData('getData', true, 'aaa');
describe('AboutUsPage', () => {
let component;
let wrapper;
wrapper = shallow(<AboutUsPage getThis={function(){component=this;}} testCore={testCore}/>);
wrapper.setState({
name: 'tom'
});
it('renders correctly', async () => {
await component.componentWillMount();
expect(wrapper.state().name).toBe('aaa');
});
});
