callstack / react-native-testing-library

🦉 Simple and complete React Native testing utilities that encourage good testing practices.
https://callstack.github.io/react-native-testing-library/
MIT License
3.09k stars 273 forks source link

React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. #1678

Closed memon07 closed 1 month ago

memon07 commented 1 month ago

I have been facing two issues mainly

console.error
Warning: React.createElement: type is invalid -- expected a string (for built-in 
  components) or a class/function (for composite components) but got: undefined. You 
  likely forgot to export your component from the file it's defined in, or you might 
   have mixed up default and named imports.

  19 |
  20 | test('renders the Example component and simulates a button press', () => {
> 21 |   const {getByTestId, getByText} = render(<Example />);
  22 |   // Check if the Text with the testID is rendered correctly
  23 |   const printedUsername = getByTestId('printed-username');

  at printWarning (node_modules/react/cjs/react.development.js:209:30)
  at error (node_modules/react/cjs/react.development.js:183:7)
  at Object.createElementWithValidation [as createElement] (node_modules/react/cjs/react.development.js:2354:7)
  at createElement (node_modules/@testing-library/react-native/src/helpers/host-component-names.tsx:35:9)
  at detectHostComponentNames (node_modules/@testing-library/react-native/src/helpers/host-component-names.tsx:27:30)
  at renderInternal (node_modules/@testing-library/react-native/src/render.tsx:48:40)
  at renderInternal (node_modules/@testing-library/react-native/src/render.tsx:29:10)
  at Object.<anonymous> (__tests__/Example1.js:20:42)

and sometimes i get this too

 FAIL  __tests__/Example1.js
  ● Test suite failed to run

  ReferenceError: expect is not defined

It seems possibly that render and expect from testing library are not properly set. i have tried a couple of fixes but noting seems to work.

can you please help me find the issue in my setup.

jest.setup.js

import 'react-native';
import React from 'react';
import 'react-native-gesture-handler/jestSetup';
import '@testing-library/react-native/extend-expect'; 

jest.mock('react-native-bootsplash', () => ({
  hide: jest.fn(),
  show: jest.fn(),
}));

jest.mock('@react-native-firebase/messaging', () => ({
   onMessage: jest.fn(),
   getToken: jest.fn(),
}));

jest.mock('react-native-encrypted-storage', () => ({
    setItem: jest.fn(),
    getItem: jest.fn(),
    removeItem: jest.fn(),
    clear: jest.fn(() => Promise.resolve()),
}));

    jest.mock('react-native-event-bus', () => ({
        getInstance: jest.fn(),
        on: jest.fn(),
        off: jest.fn(),
        emit: jest.fn(),
    }));

    jest.mock('react-native-device-info', () => ({
        getUniqueId: jest.fn(() => 'unique-id-mock'),
        getDeviceId: jest.fn(() => 'device-id-mock'),
        getVersion: jest.fn(() => '1.0.0'),
    }));

    jest.mock('react-native-flipper', () => ({
        addPlugin: jest.fn(),
        start: jest.fn(),
    }));

    jest.mock('@react-navigation/native', () => ({
        ...jest.requireActual('@react-navigation/native'),
        useNavigation: () => ({
          navigate: jest.fn(),
        }),
        NavigationContainer: ({ children }) => children,
    }));

    jest.mock('react-native-version-check', () => ({
        getLatestVersion: jest.fn().mockResolvedValue('1.0.0'),
        getCurrentVersion: jest.fn().mockReturnValue('1.0.0'),
        needUpdate: jest.fn().mockResolvedValue({
          isNeeded: false,
          storeUrl: 'https://appstore.example.com',
        }),
    }));

    jest.mock('@react-navigation/stack', () => {
        return {
            createStackNavigator: () => {
            return {
                Navigator: ({ children }) => children,
                Screen: () => null,
            };
            },
        };
    });

    jest.mock('react-native-gesture-handler', () => {
        const React = require('react');
        const { View, Text, TextInput,ScrollView,TouchableOpacity } = require('react-native');

        return {
          RNGestureHandlerRootView: View,
          TouchableWithoutFeedback: View,
          // Mock TextInput if you're using it
          TextInput: (props) => <TextInput {...props} />,
          ScrollView: (props) => <ScrollView {...props} />,
          TouchableOpacity: (props) => <TouchableOpacity {...props} />,
        };
      });

    jest.mock('@react-native-firebase/analytics', () => {
        return {
            logEvent: jest.fn(),
            setCurrentScreen: jest.fn(),
            setUserId: jest.fn(),
            setUserProperties: jest.fn(),
        };
    });

    jest.mock('react-native-animatable', () => {
        const View = require('react-native').View;
        return {
          View: View,
          Text: View,
          createAnimatableComponent: (Component) => Component,
          fadeIn: jest.fn(),
          fadeOut: jest.fn(),
          bounceIn: jest.fn(),
          bounceOut: jest.fn(),
        };
    });

    jest.mock('react-native-modal', () => {
        const React = require('react');
        const View = require('react-native').View;
        return ({ children, ...props }) => (
            <View {...props}>{children}</View>
        );
    });

    jest.mock('react-native-toast-message', () => {
        const React = require('react');
        const View = require('react-native').View;
        return {
          show: jest.fn(),
          hide: jest.fn(),
          Toast: (props) => <View {...props} />,
          BaseToast: (props) => <View {...props} />,
          ErrorToast: (props) => <View {...props} />,
          InfoToast: (props) => <View {...props} />,
          SuccessToast: (props) => <View {...props} />,
        };
    });

    jest.mock('react-native-biometrics', () => {
        return {
          BiometryTypes: {
            TouchID: 'TouchID',
            FaceID: 'FaceID',
            Biometrics: 'Biometrics',
          },
          isSensorAvailable: jest.fn(() => Promise.resolve({ available: true, biometryType: 'Biometrics' })),
          createKeys: jest.fn(() => Promise.resolve({ publicKey: 'mocked-public-key' })),
          createSignature: jest.fn(() => Promise.resolve({ success: true, signature: 'mocked-signature' })),
          simplePrompt: jest.fn(() => Promise.resolve({ success: true })),
        };
    });

    jest.mock('react-native-phone-number-input', () => {
        const React = require('react');
        const { View, TextInput } = require('react-native');

        const PhoneInput = (props) => {
          return (
            <View>
              <TextInput
                testID="phone-input"
                value={props.value}
                onChangeText={props.onChangeText}
                placeholder="Enter phone number"
              />
            </View>
          );
        };

        return PhoneInput;
    });

    jest.mock('react-native', () => {
        const actualReactNative = jest.requireActual('react-native');

        return {
          ...actualReactNative,
          requireNativeComponent: jest.fn((name) => {
            // Return a mock component based on the name
            if (name === 'SkeletonPlaceholder') {
              return jest.fn(); // Return a mock function for SkeletonPlaceholder
            }
            return jest.fn(); // For other components, return a simple mock function
          }),
        };
    });

    jest.mock('react-native-skeleton-placeholder', () => {
        return {
            __esModule: true,
            default: (props) => <div {...props} />, // Mock implementation
        };
    });

    jest.mock('react-native-snap-carousel', () => {
        return {
          Carousel: (props) => <div {...props} />, // Mock implementation
        };
    });

    jest.mock('react-native-image-slider-box', () => {
        return {
          SliderBox: (props) => <div {...props} />, // Mock implementation
        };
    });

    jest.mock('react-native-vector-icons/AntDesign', () => {
        return {
            __esModule: true, // If you are using ES6 imports
            default: () => null, // Mock implementation
        };
    });

    jest.mock('@sentry/react-native', () => ({
        captureException: jest.fn(),
        withScope: jest.fn(),
        init: jest.fn(),
        wrap: jest.fn((component) => component),
    }));

    jest.mock('@notifee/react-native', () => ({
        EventType: {
          ACTION_PRESS: 'ACTION_PRESS',
          DELIVERED: 'DELIVERED',
        },
        onForegroundEvent: jest.fn(),
        displayNotification: jest.fn(),
        cancelNotification: jest.fn(),
    }));

    jest.mock('react-native-push-notification', () => ({
        configure: jest.fn(),
        localNotification: jest.fn(),
        requestPermissions: jest.fn(),
        checkPermissions: jest.fn(),
        cancelAllLocalNotifications: jest.fn(),
    }));

    jest.mock('react-native', () => {
      const RN = jest.requireActual('react-native');

      return {
        ...RN,
        Platform: {
          ...RN.Platform,
          OS: 'ios', // or 'android', depending on your needs
          select: (obj) => obj['ios'], // or 'android' as needed
        },
      };
    });

    jest.mock('react-native', () => {
        const RN = jest.requireActual('react-native');

        return {
          ...RN,
          Platform: {
            ...RN.Platform,
            OS: 'android', // or 'android', depending on your needs
            select: (obj) => obj['android'], // or 'android' as needed
          },
        };
    });

    jest.mock('react-native-android-open-settings', () => ({
        RNAndroidOpenSettings: {
          openSettings: jest.fn(), // You can mock the methods you need
        },
    }));

    jest.mock('react-native-maps', () => {
        const React = require('react');
        const { View } = require('react-native');

        const MockMapView = (props) => {
          return React.createElement(View, props, props.children);
        };

        const MockMarker = (props) => {
          return React.createElement(View, props, props.children);
        };

        return {
          __esModule: true,
          default: MockMapView,
          Marker: MockMarker,
        };
    });

    jest.mock('react-native-gifted-chat', () => {
        return {
          GiftedChat: jest.fn((props) => <div {...props} />), // Mock the GiftedChat component
          Bubble: jest.fn((props) => <div {...props} />), // Mock the Bubble component
          Day: jest.fn((props) => <div {...props} />), // Mock the Day component
        };
    });

    jest.mock('react-native-code-push', () => {
        const cp = (_) => (app) => app;
        Object.assign(cp, {
          InstallMode: {},
          CheckFrequency: {},
          SyncStatus: {},
          UpdateState: {},
          DeploymentStatus: {},
          DEFAULT_UPDATE_DIALOG: {},

          checkForUpdate: jest.fn(),
          codePushify: jest.fn(),
          getConfiguration: jest.fn(),
          getCurrentPackage: jest.fn(),
          getUpdateMetadata: jest.fn(),
          log: jest.fn(),
          notifyAppReady: jest.fn(),
          notifyApplicationReady: jest.fn(),
          sync: jest.fn(),
        });
        return cp;
    });

    jest.mock('@sentry/react-native', () => ({
        init: jest.fn(),
        ReactNavigationInstrumentation: jest.fn(),
        ReactNativeTracing: jest.fn(),
        wrap: jest.fn((component) => component),
        TouchEventBoundary: ({ children }) => children,
    }));

    jest.mock('react-native/Libraries/Components/StatusBar/StatusBar', () => 'StatusBar');

    jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');

    jest.mock('react-native-keychain');  // Ensures Keychain is mocked

    jest.mock('react-native-webview');

    jest.mock('react-native');

    jest.mock('react-native-confirmation-code-field');

    jest.mock('react-native-document-picker');

    jest.mock('react-native-image-picker');

    jest.mock('react-native-permissions');

    jest.mock('react-native-recaptcha-that-works');

    jest.mock('react-native-file-viewer');

    jest.mock('react-native-image-zoom-viewer');

    jest.mock('rn-fetch-blob');

    jest.mock('react-native-parsed-text');

    jest.mock('react-native-restart');

    jest.mock('react-native-wheel-picker-android');

    jest.mock('@react-navigation/stack');

    jest.mock('@react-navigation/bottom-tabs');

    jest.mock('@ptomasroos/react-native-multi-slider');

    jest.mock('react-native-vector-icons');

    jest.mock('react-native-rsa-native');

    jest.mock('react-native-dropdown-picker');

    jest.mock('lottie-react-native');

    jest.mock('react-native-splash-screen');

    jest.mock('@react-native-community/netinfo');

    jest.mock('react-native/Libraries/TurboModule/TurboModuleRegistry', () => ({
        get: jest.fn(() => null),
        getEnforcing: jest.fn(() => ({})),
    }));

    jest.mock('./src/navigation/NavStack', () => 'NavStack');
    jest.mock('i18n-js'); 
    jest.mock('react-native-localize'); 

jest.config.js

module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ["./jest.setup"],
transformIgnorePatterns: [
      'node_modules/(?!(react-native|@react-native|i18n-js|@react-navigation|react- 
      native-keychain|@react-native-firebase|react-native-polyfills|react-native- 
      config|react-native-encrypted-storage|react-native-version-check|react-native- 
      device-info|react-native-flipper|redux-flipper|react-native-modal|react-native- 
      toast-message|@react-native|react-native-gesture-handler|react-native- 
      animatable|react-native-biometrics|react-native-phone-number-input|react- 
      native-confirmation-code-field|react-native-webview|react-native-document- 
      picker|react-native-image-picker|react-native-permissions|react-native- 
      recaptcha-that-works|react-native-file-viewer|react-native-image-zoom- 
      viewer|rn-fetch-blob|react-native-parsed-text|react-native-restart|react- 
      native-wheel-picker-android|react-native-skeleton-placeholder|@react-native- 
      masked-view/masked-view|react-native-linear-gradient||react-native-image- 
      slider-box|react-native-vector-icons|@sentry/react-native|react-native-radio- 
      buttons-group|@notifee/react-native|react-native-push-notification||react- 
      native-image-progress|react-native-progress||react-native-swipe-list- 
      view|react-native-date-picker|@ptomasroos/react-native-multi-slider|react- 
      native-select-dropdown|react-native-base64|react-native-gifted-charts|react- 
      native-android-open-settings|react-native-signature-canvas|react-native- 
      maps|react-native-open-maps|react-native-gifted-chat|react-native-rsa- 
      native|react-native-dropdown-picker|lottie-react-native|react-native-app-intro- 
      slider|react-native-code-push|react-native-splash-screen|@react-native- 
      community/netinfo|jail-monkey|@testing-library/react-native|@testing- 
      library/jest-native)/)',
    ],
moduleNameMapper: {
  '^react-native$': '<rootDir>/__mocks__/react-native.js',
  'react-native-vector-icons/(.*)$': '<rootDir>/__mocks__/react-native-vector- 
    icons.js',
  "^@sentry/react-native$": "<rootDir>/__mocks__/@sentry/react-native.js"
 },
transform: {
   '^.+\\.[jt]sx?$': 'babel-jest',
 },
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node','svg'],
moduleDirectories: ['node_modules', 'src'],
};

babel.config.js

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    ['@babel/plugin-transform-class-properties', { loose: true }],
    ['@babel/plugin-transform-private-methods', { loose: true }],
    ['@babel/plugin-transform-private-property-in-object', { loose: true }]
   ],
 env: {
   production: {
     plugins: ['transform-remove-console'],
   },
  },
 };

Example component

import React from 'react';
import {Button, Text, View} from 'react-native';
import {render, fireEvent} from '@testing-library/react-native';

 function Example() {
   return (
     <View>
      <Button
       title="Print Username"
       onPress={() => {
         console.log('hi');
       }}
      />
      <Text testID="printed-username">hi</Text>
     </View>
   );
 }

 test('renders the Example component and simulates a button press', () => {
    const {getByTestId, getByText} = render(<Example />);

    const printedUsername = getByTestId('printed-username');
    expect(printedUsername.props.children).toBe('hi');

    // Simulate the button press
    const button = getByText('Print Username');
    fireEvent.press(button);
   // jest.spyOn(console, 'log').mockImplementation(() => {});
});

i have tried importing expect but still the issue been found .

import { expect } from '@jest/globals';

Package.json

   "@testing-library/react-native": "^12.7.2",
   "react": "18.3.1",
   "react-native": "0.71.6",
   "@babel/core": "^7.25.8",
   "@babel/plugin-transform-class-properties": "^7.25.7",
   "@babel/plugin-transform-private-methods": "^7.25.7",
   "@babel/plugin-transform-private-property-in-object": 
     "^7.25.8",
   "@babel/preset-env": "^7.20.0",
   "@babel/preset-react": "^7.25.7",
   "@babel/runtime": "^7.20.0",
   "jest": "^29.7.0",
   "react-test-renderer": "^18.3.1",
mdjastrzebski commented 1 month ago

Error "Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined." usuallymeans that you mocked some module, yet missed to mock one (or more) components exported from that module (hence is undefined).

It's hard to tell the exact culprit since you have a large amount of mocks. I recommend to disable them gradually (or disable all and enable them gradually) to narrow down which one is causing the issue.