I've searched many a thread on this issue on the site but none of them have answers or answers that work for me. I have a jest unit test suite that I'm attempting to run but keep getting Test suite failed to run -- TypeError: Cannot read property 'fetch' of undefined
error messages.
I've already tried all of the fixes suggested on all of the other stackoverflow and github issue threads related to this issue.
Can anyone help me?
Here's one of my two failing tests: 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');
});
});
Here's the second test: 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');
});
});
});
Here's my 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"
}
}
}
}
Here's my node_modules/jest-expo/src/setup.js
file
(changing require('whatwg-fetch')
to require('fetch-everything)
didn't fix the issue btw.
/**
* 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;
});