如何在未弹出的情况下使用Expo(默认)为Create React Native App(CRNA)创建适用于组件,Redux操作和Reducers的所有测试?
还通过Expo使用Axios,Redux-Thunk异步操作和React-Native Maps。
答案 0 :(得分:4)
在阅读并重新阅读Jest,Enzyme和Redux的相关文档后, 以及特定NPM 软件包版本的Google搜索问题我对此进行了整理。
有许多"移动部件"因为所有NPM包必须一起玩得很好。 I.E测试,模拟,redux和你的React味道。
这里有什么效果(2018-01-16)。
Redux操作,缩减器和组件的工作测试。
{
"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,
};
});
要使用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',
};
这里我们在动作测试中使用了上述配置。
// 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
});
});
确保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,
});
});
});