Много проблем с jest-expo - «TypeError: Невозможно прочитать свойство« извлечение »из неопределенного» - PullRequest
0 голосов
/ 27 февраля 2019

Я искал много веток по этому вопросу на сайте, но ни у одного из них нет ответов или ответов, которые мне подходят.У меня есть набор юнит-тестов Jest, который я пытаюсь запустить, но продолжаю получать 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');
    });
  });
});

Вот мой пакет.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;
});
...