在未弹出 Redux 的情况下测试 Jest React-Native Expo CRNA

Posted

技术标签:

【中文标题】在未弹出 Redux 的情况下测试 Jest React-Native Expo CRNA【英文标题】:Test Jest React-Native Expo CRNA with Redux not ejected 【发布时间】:2018-06-25 04:21:06 【问题描述】:

如何在未弹出的情况下使用 Expo(默认)让组件、Redux Actions 和 Reducers 的所有测试工作,以使用 Expo(默认)创建 React Native App (CRNA)?

还通过 Expo 使用 Axios、Redux-Thunk 异步操作和 React-Native Maps。

【问题讨论】:

【参考方案1】:

在阅读和重新阅读Jest、Enzyme 和Redux 的相关文档之后, 以及特定 NPM 包版本的谷歌搜索问题,我对此进行了整理。

所有 NPM 包都必须协同工作,这其中有很多“可动部分”。 IE 测试、模拟、redux 和你的 React 风格。

这是目前有效的方法 (2018-01-16)。

设置

环境

OS X High Sierra Visual Studio 代码

项目平台

创建 React Native 应用程序 (CRNA) 23.0.4 世博会 反应 16.0.0-alpha.12 React-Native 0.50.3

测试框架

开玩笑^22.0.6 Jest-CLI ^22.0.6 Jest-酶 ^4.0.2 Jest-Expo ^22.0.0 React-addons-test-utils ^15.6.2 React-DOM 16.0.0-beta.5

package.json

Redux 操作、reducer 和组件的工作测试。


"name": "MyApp",
"version": "0.0.1",
"private": true,
"author": "Thomas Hagström <thomas@crossplatform.se>",
"devDependencies": 
"axios-mock-adapter": "^1.10.0",
"babel": "^6.3.26",
"babel-eslint": "^8.2.1",
"babel-jest": "^22.0.6",
"babel-polyfill": "^6.16.0",
"babel-preset-airbnb": "^1.0.1",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babel-preset-react-native": "1.9.0",
"eslint": "^4.15.0",
"eslint-config-airbnb": "^16.1.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.5.1",
"jest": "^22.0.6",
"jest-cli": "^22.0.6",
"jest-enzyme": "^4.0.2",
"jest-expo": "^22.0.0",
"react-addons-test-utils": "^15.6.2",
"react-dom": "^16.0.0-beta.5",
"react-native-mock": "^0.3.1",
"react-native-scripts": "1.8.1",
"react-test-renderer": "^16.0.0-alpha.12",
"remotedev-rn-debugger": "^0.8.3"
,
"babel": 
"presets": [
    "es2015",
    "react"
]
,
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"scripts": 
"start": "react-native-scripts start",
"eject": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"test": "node node_modules/jest/bin/jest.js --watch",
"postinstall": "remotedev-debugger --hostname localhost --port 5678 --injectserver",
"eslint": "./node_modules/.bin/eslint"
,
"remotedev": 
"hostname": "localhost",
"port": 5678
,
"jest": 
"preset": "jest-expo",
"transformIgnorePatterns": [
    "node_modules/(?!(react-native|jest-resolve|expo|lodash|enzyme|prop-types|react|jest-enzyme|enzyme|jest-expo|jest-serializer-enzyme|react-native-elements|react-native-google-places-autocomplete)/)"
],
"setupFiles": [
    "./config/jest/globalFetch.js",
    "./config/enzyme/index.js"
]
,
"dependencies": 
"@expo/vector-icons": "^6.2.2",
"axios": "^0.17.1",
"expo": "^23.0.4",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"lodash": "^4.17.4",
"prop-types": "^15.6.0",
"react": "16.0.0-alpha.12",
"react-native": "0.50.3",
"react-native-elements": "^0.18.5",
"react-native-google-places-autocomplete": "^1.3.6",
"react-native-maps": "^0.18.0",
"react-navigation": "^1.0.0-beta.23",
"react-navigation-redux": "^0.1.0",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
"redux-promise": "^0.5.3",
"redux-thunk": "^2.2.0",
"redux-mock-store": "^1.4.0",
"remote-redux-devtools": "^0.5.12",
"socketcluster-server": "^9.1.2"


酶全局配置

Enzyme 的配置脚本,见下面的package.json,如下所示。

// config/enzyme/index.js
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

// Setup enzyme's react adapter
Enzyme.configure( adapter: new Adapter() );

示例

全球模拟

展览套件

在我的项目的根目录中,我将模拟放在__mocks__ 目录中,这样它们就会被 Jest 自动拾取。

这将解决使用本地移动 API 调用的情况 - 特别是 ExpoKit SDK - 而不仅仅是 HTTP REST。

// __mocks__/expo.js
jest.mock('expo', () => 
const expo = require.requireActual('expo');

const positionMock = 
    latitude: 1,
    longitude: 1,
;

// Mock the expo library
return 
    Location: 
        setApiKey: jest.fn(),
        getCurrentPositionAsync:
            options =>
                new Promise(
                    resolve => resolve(options ? 
                        coords: positionMock,
                     : null)
                    , null,
                )
        ,
    ,
    Constants: 
        manifest: 
            extra:  google:  maps: 'Your-API-KEY-HERE'  ,
        ,
    ,
    Permissions: 
        LOCATION: 'location',
        askAsync: type => new Promise(resolve =>
            resolve(type === 'location' ?
                 status: 'granted' 
                : null)),
    ,
    ...expo,
;
);

Redux - 模拟 - 商店

要使用 Thunk 配置 Redux,因此您不必在每次(操作)测试之前都这样做。在您的测试中导入 redux-mock-store 的含义将使用以下实现:

// __mocks__/redux-mock-store.js
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

export default mockStore;

常量

用作 redux 操作类型。

// src/Constants.js
const MapConstants = 
MAP_LOCATION_CHANGED: 'MAP REGION CHANGED',
MAP_LOCATION_BUSY: 'MAP: GETTING LOCATION',
MAP_LOCATION_SUCCESS: 'MAP: GET LOCATION SUCCESS',
MAP_LOCATION_FAILED: 'MAP: GET LOCATION FAILED',
;

Redux 动作创建者

这里我们在动作测试中使用了上面的配置。

// src/Actions/__tests__/MapActions.test.js
import configureMockStore from 'redux-mock-store';

import  MapConstants  from '../../Constants';
import 
    GetLocation
 from '../MapActions';

const store = configureMockStore();

describe('map actions', () => 
    beforeEach(() => 
        store.clearActions();
    );

    test('GetLocation returns SUCCESS when done', async () => 
        const expectedPayload =  latitude: 1, longitude: 1 ;
        const expectedActions = [
             type: MapConstants.MAP_LOCATION_BUSY ,
             type: MapConstants.MAP_LOCATION_SUCCESS, payload: expectedPayload ,
        ];

        // Dispatch action
        await store.dispatch(GetLocation());

        expect(store.getActions()).toMatchSnapshot();
        expect(store.getActions()).toEqual(expectedActions);
        );
);

组件

我使用纯组件并在单独的容器上连接我的 redux。

import React from 'react';
import  shallow  from 'enzyme';

import Map from '../Map';
import  Colors  from '../../styles';

// src/Components/__tests__/map.test.js

function setup () 
    const props = 
        GetLocation: jest.fn(),
        LocationChanged: jest.fn(),
        map: 
            isBusy: false,
            hasError: false,
            errorMessage: null,
            location: null,
            region: 
                latitude: 45.52220671242907,
                longitude: -122.6653281029795,
                latitudeDelta: 0.04864195044303443,
                longitudeDelta: 0.040142817690068,
            ,
        ,
    ;

    const enzymeWrapper = shallow(<Map ...props />);

    return 
        props,
        enzymeWrapper,
    ;


describe('components', () => 
    describe('Map', () => 
        it('should render self and subcomponents', () => 
            const  enzymeWrapper  = setup();
            expect(enzymeWrapper).toMatchSnapshot();

            const busyProps = enzymeWrapper.find('BusyIndicator').props();
            expect(busyProps.isBusy).toBe(false);
            expect(busyProps.loadingIndicatorColor).toEqual("#FFFFFF");
        );

        // TODO: Mock map functions
    );
);

Redux 减速器

确保reducer返回正确的状态并且不会改变它。

import MapReducer from '../MapReducer';
import  MapConstants  from '../../Constants';

describe('MapReducer', () => 
    test('should return the initial state', () => 
        expect(MapReducer(undefined, ))
            .toEqual(
                isBusy: false,
                hasError: false,
                errorMessage: null,
                location: null,
                region: 
                    latitude: 45.52220671242907,
                    longitude: -122.6653281029795,
                    latitudeDelta: 0.04864195044303443,
                    longitudeDelta: 0.040142817690068,
                ,
            );
    );

    test(`should handle $MapConstants.MAP_LOCATION_BUSY`, () => 
        expect(MapReducer(, 
            type: MapConstants.MAP_LOCATION_BUSY,
        ))
            .toEqual(
                hasError: false,
                errorMessage: null,
                isBusy: true,
                type: MapConstants.MAP_LOCATION_BUSY,
            );
    );

    test(`should handle $MapConstants.MAP_LOCATION_SUCCESS`, () => 
        const resultArray = ['test'];
        expect(MapReducer(, 
            type: MapConstants.MAP_LOCATION_SUCCESS,
            payload: resultArray,
        ))
            .toEqual(
                isBusy: false,
                hasError: false,
                errorMessage: null,
                location: resultArray,
                type: MapConstants.MAP_LOCATION_SUCCESS,
            );
    );

    test(`should handle $MapConstants.MAP_LOCATION_FAILED`, () => 
        const errorString = 'test error';
        expect(MapReducer(, 
            type: MapConstants.MAP_LOCATION_FAILED,
            payload: errorString,
        ))
            .toEqual(
                isBusy: false,
                hasError: true,
                errorMessage: errorString,
                location: null,
                type: MapConstants.MAP_LOCATION_FAILED,
            );
    );

    test(`should handle $MapConstants.MAP_LOCATION_CHANGED`, () => 
        const resultArray = ['test'];
        expect(MapReducer(, 
            type: MapConstants.MAP_LOCATION_CHANGED,
            payload: resultArray,
        ))
            .toEqual(
                isBusy: false,
                hasError: false,
                errorMessage: null,
                region: resultArray,
                type: MapConstants.MAP_LOCATION_CHANGED,
            );
    );
);

【讨论】:

以上是关于在未弹出 Redux 的情况下测试 Jest React-Native Expo CRNA的主要内容,如果未能解决你的问题,请参考以下文章

测试 createAsyncThunk Redux Toolkit Jest

测试库 React 与 Jest

使用 Jest 测试 redux 存储

使用 jest 进行 Redux 表单测试

如何用 jest 测试 redux saga?

reactjs - jest 快照测试嵌套的 redux “连接”组件