Search code examples
javascriptreactjsreact-nativejestjsts-jest

Jest returns empty object for all node_modules imports


This is my jest config

const {defaults: tsjPreset} = require('ts-jest/presets');
const {pathsToModuleNameMapper} = require('ts-jest');
/**
 * This will fail if the tsconfig is not properly formatted as json
 * use this to format it correctly
 * https://jsonformatter.org/json-parser
 */
const {compilerOptions} = require('./tsconfig.json');

module.exports = {
  ...tsjPreset,
  preset: 'react-native',
  modulePathIgnorePatterns: ['<rootDir>/assets'],
  /**
   * required so that ts-jest can identify aliases as mentioned in tsconfig
   * https://kulshekhar.github.io/ts-jest/docs/getting-started/paths-mapping/
   */
  modulePaths: [compilerOptions.baseUrl],
  /**
   * using module name mapper to:
   * - expose the alias paths to ts-jest from tsconfig (babel-jest seems to read it automatically from babel config)
   * Note that the baseUrl alias is handled above by modulePaths
   * - replace assets imports with a mock implementation
   * https://jestjs.io/docs/29.2/webpack
   * Note that the order of rules is important, as the first matched rule is applied.
   * We want to make sure that files with below extensions are matched by the first rule and get mocked using fileMock.js
   */
  moduleNameMapper: {
    '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
      '<rootDir>/__mocks__/fileMock.js',
    ...pathsToModuleNameMapper(compilerOptions.paths, {
      prefix: '<rootDir>/app',
    }),
  },
  setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
  /**
   * by default jest does not trasnform node_modules, so if we are consuming any source code from node_modules
   * we need to add it here so that jest can transpile it before consuming
   *
   * It seems there are a lot of react-native libraries which need to be transpiled as well,
   * as they contain some or the other files which are not build output
   *
   * Note that adding multiple rules in the array as separate entries does not seem to work as expected
   */
  transformIgnorePatterns: [
    'node_modules/(?!(@react-native|react-native|@react-native-firebase)/)',
  ],
  /**
   * we need ts-jest for transforming typescript files
   */
  transform: {
    '^.+\\.jsx$': 'babel-jest',
    '^.+\\.tsx?$': [
      'ts-jest',
      {
        tsconfig: 'tsconfig.spec.json',
      },
    ],
  },
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};

When i write tests for the below component:

import React from 'react';
import LinearGradient from 'react-native-linear-gradient';
import {useAppTheme} from '@util/hookUtils';
import {StyleSheet} from 'react-native';

const ScrollOverflow = (): JSX.Element => {
  const theme = useAppTheme();

  const colors = theme.isDark
    ? [theme.secondaryDarkColor, theme.secondaryColor]
    : [theme.primaryDarkColor, theme.primaryColor];

  return <LinearGradient style={styles.scrollOverview} colors={colors} />;
};

export default ScrollOverflow;

I get an error from jest saying:

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

When i debug this using breakpoints, i can see that react-native-linear-gradient gets imported as:

react-native-linear-graident1: { default: {} };

while my local components are imported with non empty default and work correctly.

This happens for any other components that I load from node_modules, like react-native-modal

I believe this has something to do with jest transforming or not transforming node_modules, but i haven't been able to make it work by changing the various parameters in jest config like trasnformIgnorePatterns and others.

Any pointers?


Solution

  • After further debugging, I realized that this was happening because someone else had already created global mocks for these modules using jest.mock('react-native-modal')

    This was not immediately clear as global mocks are not visible in the local test file unless you already know that the global mock exists.

    I was able to debug by adding breakpoints and then going into the jest module resolution, which was resolving this as a mock. That is when i realized that this module might already be mocked.