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

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

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

【问题讨论】:

    标签: react-native redux jestjs expo create-react-native-app


    【解决方案1】:

    在阅读和重新阅读JestEnzymeRedux 的相关文档之后, 以及特定 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,
                });
        });
    });
    

    【讨论】:

      猜你喜欢
      • 2018-03-17
      • 2020-09-20
      • 2019-03-22
      • 2021-04-24
      • 2019-07-07
      • 1970-01-01
      • 2021-02-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多