在网上找了好久针对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'); }); });