我在网站上搜索了很多关于这个问题的主题,但没有一个答案或答案对我有用。我有一个开玩笑的单元测试套件,我正在尝试运行但不断收到Test suite failed to run -- TypeError: Cannot read property 'fetch' of undefined
错误消息。
我已经尝试了与此问题相关的所有其他 stackoverflow 和 github 问题线程上建议的所有修复。
谁能帮我?
这是我的两个失败测试之一:App.spec.js
import React from 'react';
import renderer from 'react-test-renderer';
import { mount } from 'enzyme';
import { App } from '../../App';
import { FlooringType } from '../../Condition/FlooringType';
import { SavedPhotosLauncher } from '../../Components/SavedPhotosLauncher';
import { CameraLauncher } from '../../Components/CameraLauncher';
import { CommentsLauncher } from '../../Components/CommentsLauncher';
import { FloorLevel } from '../../Components/FloorLevel';
import { CeilingType } from '../../Components/CeilingType';
import { RoomLayout } from '../../Components/RoomLayout';
import "babel-polyfill";
import fetch from 'isomorphic-fetch'
global.fetch = fetch;
describe('KnockKitchenDetail view suite', () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it('should render Kitchen view correctly', () => {
const tree = renderer.create(
<App />
).toJSON()
expect( tree ).toMatchSnapshot();
});
// Room Layout header
it('should show the Room Layout header text', () => {
// Info text visible
const wrapper = mount(<RoomLayout />);
expect(wrapper.find({testID:'roomLayoutText'}).text()).toBe('Room Layout');
});
it('should show info text for what to do in Room Layout container', () => {
// Info text visible
const wrapper = mount(<RoomLayout />);
expect(wrapper.find({testID:'infoText'}).text()).toBe('Take photos from opposite corners of the room');
});
// Saved Photos and Camera Launcher Icon Buttons
it('tapping saved photos button should launch saved photos workflow',async () => {
// Select the saved photos button
const launchSavedPhotosViewer = jest.fn();
const wrapper = mount(<SavedPhotosLauncher />);
wrapper.setProps({ photoNumber: 2})
wrapper.find({testID: '{photoNumber}_of_photos'}).simulate('press');
expect(launchSavedPhotosViewer).toHaveBeenCalledTimes(1);
});
it('should launch the camera when any camera icon is selected', () => {
// Finds, selects, and launches native camera
const launchNativeCamera = jest.fn();
const wrapper = mount(<CameraLauncher />);
wrapper.find({testID: 'cameraIconLauncherBtn'}).simulate('press');
expect(launchNativeCamera).toHaveBeenCalled();
});
// Add Comments and Comment Modal Launcher Icon Buttons
it('should launch the comment modal when any comment icon is selected', () => {
// Find and select a comment icon button
const launchCommentModal = jest.fn();
const wrapper = mount(<CommentsLauncher />);
wrapper.find({testID: 'commentsIconButton'}).simulate('press');
expect(launchCommentModal).toHaveBeenCalled();
});
it('should launch the comment modal when add comments button is pressed', () => {
// Find and press the add comments button
const launchAddCommentModal = jest.fn();
const wrapper = mount(<AddCommentsLauncher />);
wrapper.find({testID: 'addCommentsRoomLayoutButton'}).simulate('press');
expect(launchAddCommentModal).toHaveBeenCalled();
});
// Floor Level Section
it('should allow user to select a floor level', () => {
// selects the floor level
const selectFloorLevel = jest.fn();
const wrapper = mount(<FloorLevel onPress={selectFloorLevel} />);
wrapper.find({testID: 'floorLevelLower'}).simulate('press')
expect(floorLevel).toHaveBeenCalledWith('Lower')
});
it('should show changed state of the floor level', () => {
// checks that the floor level state has changed
const wrapper = mount(<App />);
const text = wrapper.find({testID: 'floorLevelLower'}).text();
expect(text).toBeFalsy();
expect(wrapper.state('floorLevel')).toBe(null);
wrapper.find({testID: 'floorLevelLower'}).simulate('press');
expect(wrapper.find({testID:'floorLevelLower'}).text()).toBe('Lower');
expect(wrapper.state('floorLevel')).toBe('Lower');
});
// Walls / Paint Header Text
it('should show the walls and paint header text', () => {
// Info text visible
const wrapper = mount(<App />);
expect(wrapper.find({testID:'wallsPaintHeaderText'}).text()).toBe('Walls / Paint');
});
// Ceiling Section
it('should show the ceiling header text', () => {
// Info text visible
const wrapper = mount(<App />);
expect(wrapper.find({testID:'ceilingHeaderText'}).text()).toBe('Ceiling');
});
it('should allow user to select a type of ceiling', () => {
// selects the type of ceiling
const selectCeilingType = jest.fn();
const wrapper = mount(<CeilingType onPress={selectCeilingType} />);
wrapper.find({testID: 'ceilingTypeSpackled'}).simulate('press')
expect(selectCeilingType).toHaveBeenCalledWith('Spackled')
});
it('should show changed state of the selected ceiling type', () => {
// checks the ceiling type state change
const wrapper = mount(<App />);
const text = wrapper.find({testID: 'ceilingTypeSpackled'}).text();
expect(text).toBeFalsy();
expect(wrapper.state('ceilingType')).toBe(null);
wrapper.find({testID: 'ceilingTypeSpackled'}).simulate('press');
expect(wrapper.find({testID:'ceilingTypeSpackled'}).text()).toBe('Spackled');
expect(wrapper.state('ceilingType')).toBe('Spackled');
});
it('should show changed state of the selected condition', () => {
// checks the condition state change
const wrapper = mount(<App />);
const text = wrapper.find({testID: 'roomItemCondition'}).text();
expect(text).toBeFalsy();
expect(wrapper.state('roomItemCondition')).toBe(null);
wrapper.find({testID: 'conditionGood'}).simulate('press');
expect(wrapper.find({testID:'conditionGood'}).text()).toBe('Good');
expect(wrapper.state('roomItemCondition')).toBe('Good');
});
// Flooring Type Section
it('should show the Flooring header text', () => {
// Info text visible
const wrapper = mount(<App />);
expect(wrapper.find({testID:'flooringHeaderText'}).text()).toBe('Flooring');
});
it('should allow user to select a type of flooring', () => {
// selects the type of flooring
const selectFloorType = jest.fn();
const wrapper = mount(<FlooringType onPress={selectFloorType} />);
wrapper.find({testID: 'flooringTypeTile'}).simulate('press')
expect(selectFloorType).toHaveBeenCalledWith('Tile')
});
it('should show changed state of the selected flooring type', () => {
// checks the flooring type state change
const wrapper = mount(<App />);
const text = wrapper.find({testID: 'flooringTypeTile'}).text();
expect(text).toBeFalsy();
expect(wrapper.state('tile')).toBe(null);
wrapper.find({testID: 'flooringTypeTile'}).simulate('press');
expect(wrapper.find({testID:'selectedFlooringType'}).text()).toBe('Tile');
expect(wrapper.state('flooringType')).toBe('Tile');
});
});
这是第二个测试:RoomLayout.spec.test
import React, { Component } from 'react';
import { render } from 'react-native-testing-library';
import App from '../../App';
import fetch from 'whatwg-fetch';
global.fetch = fetch;
describe('NewMessageForm', () => {
describe('clicking send', () => {
const kitchenHeader = 'Kitchen';
let getByTestId;
beforeEach(() => {
({ getByTestId } = render(<App />));
// fireEvent.changeText(getByTestId('messageText'), messageText);
// fireEvent.press(getByTestId('sendButton'));
});
it('clears the message field', () => {
expect(getByTestId(kitchenHeader).props.value).toEqual('Kitchen');
});
});
});
这是我的 package.json
{
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"test": "node_modules/.bin/jest test/**/*.spec.js",
"eject": "expo eject"
},
"dependencies": {
"@expo/vector-icons": "^9.0.0",
"apsl-react-native-button": "^3.1.1",
"expo": "^32.0.0",
"react": "16.5.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
"react-native-camera": "git+https://git@github.com/react-native-community/react-native-camera.git",
"react-native-camera-roll-picker": "^1.2.3",
"react-native-elements": "^1.1.0",
"react-native-fontawesome": "^6.0.1",
"react-native-is-iphonex": "^1.0.1",
"react-native-vector-icons": "^6.2.0",
"react-navigation": "^3.3.2"
},
"devDependencies": {
"@babel/core": "^7.3.4",
"@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"babel-jest": "^24.1.0",
"babel-preset-expo": "^5.0.0",
"babel-preset-react-native": "^4.0.1",
"detox": "^10.0.10",
"detox-expo-helpers": "^0.6.0",
"enzyme": "^3.9.0",
"expo-detox-hook": "^1.0.10",
"jest": "^24.1.0",
"jest-expo": "^32.0.0",
"metro-react-native-babel-preset": "^0.52.0",
"prop-types": "^15.7.2",
"react-addons-test-utils": "^15.6.2",
"react-dom": "^16.8.3",
"react-native-dotenv": "^0.2.0",
"react-native-testing-library": "^1.5.0",
"react-test-renderer": "^16.8.3"
},
"jest": {
"preset": "jest-expo"
},
"private": true,
"detox": {
"test-runner": "jest",
"configurations": {
"ios.sim": {
"binaryPath": "bin/Exponent.app",
"type": "ios.simulator",
"name": "iPhone X"
}
}
}
}
这是我的node_modules/jest-expo/src/setup.js
文件(更改require('whatwg-fetch')
为require('fetch-everything)
没有解决问题顺便说一句。
/**
* Adds Expo-related mocks to the Jest environment. Jest runs this setup module
* after it runs the React Native setup module.
*/
'use strict';
const { Response, Request, Headers, fetch } = require('whatwg-fetch');
global.Response = Response;
global.Request = Request;
global.Headers = Headers;
global.fetch = fetch;
const mockNativeModules = require('react-native/Libraries/BatchedBridge/NativeModules');
const createMockConstants = require('./createMockConstants');
// window isn't defined as of react-native 0.45+ it seems
if (typeof window !== 'object') {
global.window = global;
global.window.navigator = {};
}
const mockImageLoader = {
configurable: true,
enumerable: true,
get: () => ({
prefetchImage: jest.fn(),
getSize: jest.fn((uri, success) => process.nextTick(() => success(320, 240))),
}),
};
Object.defineProperty(mockNativeModules, 'ImageLoader', mockImageLoader);
Object.defineProperty(mockNativeModules, 'ImageViewManager', mockImageLoader);
const mockPlatformConstants = {
configurable: true,
enumerable: true,
get: () => ({
forceTouchAvailable: true,
}),
};
Object.defineProperty(mockNativeModules, 'PlatformConstants', mockPlatformConstants);
const publicExpoModules = require('./expoModules');
const internalExpoModules = require('./internalExpoModules');
const expoModules = {
...publicExpoModules,
...internalExpoModules,
};
function mock(property, customMock) {
const propertyType = property.type;
let mockValue;
if (customMock !== undefined) {
mockValue = customMock;
} else if (propertyType === 'function') {
if (property.functionType === 'promise') {
mockValue = jest.fn(() => Promise.resolve());
} else {
mockValue = jest.fn();
}
} else if (propertyType === 'number') {
mockValue = 1;
} else if (propertyType === 'string') {
mockValue = 'mock';
} else if (propertyType === 'array') {
mockValue = [];
} else if (propertyType === 'mock') {
mockValue = mockByMockDefinition(property.mockDefinition);
} else {
mockValue = {};
}
return mockValue;
}
function mockProperties(moduleProperties, customMocks) {
const mockedProperties = {};
for (let propertyName of Object.keys(moduleProperties)) {
const property = moduleProperties[propertyName];
const customMock =
customMocks && customMocks.hasOwnProperty(propertyName)
? customMocks[propertyName]
: property.mock;
mockedProperties[propertyName] = mock(property, customMock);
}
return mockedProperties;
}
function mockByMockDefinition(definition) {
const mock = {};
for (const key of Object.keys(definition)) {
mock[key] = mockProperties(definition[key]);
}
return mock;
}
for (let moduleName of Object.keys(expoModules)) {
const moduleProperties = expoModules[moduleName];
const mockedProperties = mockProperties(moduleProperties);
Object.defineProperty(mockNativeModules, moduleName, {
configurable: true,
enumerable: true,
get: () => mockedProperties,
});
}
mockNativeModules.ExpoNativeModuleProxy.viewManagersNames.forEach(viewManagerName => {
Object.defineProperty(mockNativeModules.UIManager, `ViewManagerAdapter_${viewManagerName}`, {
get: () => ({
NativeProps: {},
directEventTypes: [],
}),
});
});
// Needed for `react-native-gesture-handler` as of 10/29/2018
// Otherwise the following line fails with "cannot read property directEventTypes of undefined"
// https://github.com/kmagiera/react-native-gesture-handler/blob/master/GestureHandler.js#L46
Object.defineProperty(mockNativeModules.UIManager, 'RCTView', {
get: () => ({
NativeProps: {},
directEventTypes: [],
}),
});
const modulesConstants = mockNativeModules.ExpoNativeModuleProxy.modulesConstants;
mockNativeModules.ExpoNativeModuleProxy.modulesConstants = {
...modulesConstants,
ExponentConstants: {
...modulesConstants.ExponentConstants,
...createMockConstants(),
},
};
jest.mock('expo-file-system', () => ({
FileSystem: {
downloadAsync: jest.fn(() => Promise.resolve({ md5: 'md5', uri: 'uri' })),
getInfoAsync: jest.fn(() => Promise.resolve({ exists: true, md5: 'md5', uri: 'uri' })),
readAsStringAsync: jest.fn(),
writeAsStringAsync: jest.fn(),
deleteAsync: jest.fn(),
moveAsync: jest.fn(),
copyAsync: jest.fn(),
makeDirectoryAsync: jest.fn(),
readDirectoryAsync: jest.fn(),
createDownloadResumable: jest.fn(),
},
}));
jest.mock('react-native/Libraries/Image/AssetRegistry', () => ({
registerAsset: jest.fn(() => 1),
getAssetByID: jest.fn(() => ({
fileSystemLocation: '/full/path/to/directory',
httpServerLocation: '/assets/full/path/to/directory',
scales: [1],
fileHashes: ['md5'],
name: 'name',
exists: true,
type: 'type',
hash: 'md5',
uri: 'uri',
width: 1,
height: 1,
})),
}));
jest.mock('react-native-gesture-handler', () => {
const View = require('react-native/Libraries/Components/View/View');
return {
Swipeable: View,
DrawerLayout: View,
State: {},
ScrollView: View,
Slider: View,
Switch: View,
TextInput: View,
ToolbarAndroid: View,
ViewPagerAndroid: View,
DrawerLayoutAndroid: View,
WebView: View,
NativeViewGestureHandler: View,
TapGestureHandler: View,
FlingGestureHandler: View,
ForceTouchGestureHandler: View,
LongPressGestureHandler: View,
PanGestureHandler: View,
PinchGestureHandler: View,
RotationGestureHandler: View,
/* Buttons */
RawButton: View,
BaseButton: View,
RectButton: View,
BorderlessButton: View,
/* Other */
FlatList: View,
gestureHandlerRootHOC: jest.fn(),
Directions: {},
};
});
jest.doMock('react-native/Libraries/BatchedBridge/NativeModules', () => mockNativeModules);
jest.mock('expo-react-native-adapter', () => {
const ExpoReactNativeAdapter = require.requireActual('expo-react-native-adapter');
const { NativeModulesProxy } = ExpoReactNativeAdapter;
// After the NativeModules mock is set up, we can mock NativeModuleProxy's functions that call
// into the native proxy module. We're not really interested in checking whether the underlying
// method is called, just that the proxy method is called, since we have unit tests for the
// adapter and believe it works correctly.
//
// NOTE: The adapter validates the number of arguments, which we don't do in the mocked functions.
// This means the mock functions will not throw validation errors the way they would in an app.
for (const moduleName of Object.keys(NativeModulesProxy)) {
const nativeModule = NativeModulesProxy[moduleName];
for (const propertyName of Object.keys(nativeModule)) {
if (typeof nativeModule[propertyName] === 'function') {
nativeModule[propertyName] = jest.fn(async () => {});
}
}
}
return ExpoReactNativeAdapter;
});