react-native: 0.61.0 - Jest "Cannot find module ..."

After upgrading from 0.60.5 to 0.61.0, when I run Jest tests I will get errors anywhere I was mocking a component or native module from React Native using the syntax where I mock the module/component by name rather than providing a path to it or mocking all of React Native, i.e:

jest.mock('TextInput', () => {
  ...
})

This has always worked in previous versions of React Native and is still mentioned as a valid way to mock things in the jest docs.

It looks like I can fix the issue by mocking all of React Native and overriding the specific modules I care about, however I’m curious if this change was intentional or not. If it is, then I can move this issue to the Jest repo and let them know they need to update their docs.

React Native version: System: OS: macOS 10.14.6 CPU: (8) x64 Intel® Core™ i7-7920HQ CPU @ 3.10GHz Memory: 1.01 GB / 16.00 GB Shell: 3.2.57 - /bin/bash Binaries: Node: 8.11.3 - ~/.nvm/versions/node/v8.11.3/bin/node Yarn: 1.16.0 - /usr/local/bin/yarn npm: 5.6.0 - ~/.nvm/versions/node/v8.11.3/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman SDKs: iOS SDK: Platforms: iOS 13.0, DriverKit 19.0, macOS 10.15, tvOS 13.0, watchOS 6.0 Android SDK: Build Tools: 25.0.2 IDEs: Android Studio: 3.5 AI-191.8026.42.35.5791312 Xcode: 11.0/11A420a - /usr/bin/xcodebuild npmPackages: react: 16.9.0 => 16.9.0 react-native: 0.61.1 => 0.61.1 npmGlobalPackages: react-native-cli: 2.0.1

Steps To Reproduce

  1. Using a 0.61.0 build of RN, write a Jest test(doesn’t have to assert anything in particular).
  2. In that test, mock a React Native library component by adding jest.mock('View')
  3. Run the test

Describe what you expected to happen: Jest should fail with an error telling you Cannot find module 'View' ...

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 16
  • Comments: 37 (5 by maintainers)

Commits related to this issue

Most upvoted comments

I was able to successfully mock the individual modules by mocking the direct path in react-native so items like

jest.mock('Keyboard', () => {})
jest.mock('TextInput', () => {})

can become

jest.mock('react-native/Libraries/Components/Keyboard/Keyboard', () => {})
jest.mock('react-native/Libraries/Components/TextInput/TextInput'), () => {})

Prior to rn 61 haste was used to map these files. In haste the mapping is done by filename so it’s easy enough to find the corresponding file in the react-native repo by using find files search provided by github.

@fossage This is a working example that mocks a NativeModule and parts of a JS module (LayoutAnimation). It lets you import a pre-mocked instance of react-native, override whatever you’d like, and then access those mocks from your tests:

jest.config.js

module.exports = {
  preset: 'react-native',
  setupFiles: ['<rootDir>/setup.js'],
};

setup.js

import * as ReactNative from 'react-native';

jest.doMock('react-native', () => {
  // Extend ReactNative
  return Object.setPrototypeOf(
    {
      // Redefine an export, like a component
      Button: 'MockedButton',

      // Mock out properties of an already mocked export
      LayoutAnimation: {
        ...ReactNative.LayoutAnimation,
        configureNext: jest.fn(),
      },

      // Mock a native module
      NativeModules: {
        ...ReactNative.NativeModules,
        Override: {great: 'success'},
      },
    },
    ReactNative,
  );
});

__tests__/example-test.js

import { LayoutAnimation, NativeModules } from 'react-native';

test(`NativeModules contains overrides`, () => {
  expect(NativeModules.Override).toEqual({ great: 'success' });
});

test(`other mock NativeModules are preserved`, () => {
  expect(NativeModules.Timing).toBeDefined();
  expect(NativeModules.Timing.createTimer).toBeDefined();
  expect(NativeModules.Timing.createTimer.mock).toBeDefined();
});

test(`LayoutAnimation.configureNext is mocked`, () => {
  expect(LayoutAnimation).toBeDefined();
  expect(LayoutAnimation.configureNext).toBeDefined();
  expect(LayoutAnimation.configureNext.mock).toBeDefined();
});

Output

$ jest
 PASS  __tests__/App-test.js
  ✓ NativeModules contains overrides (18ms)
  ✓ other mock NativeModules are preserved (4ms)
  ✓ LayoutAnimation.configureNext is mocked (3ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        7.328s
Ran all test suites.

The main limitation that comes to mind is that you need to use the same doMock call for all tests and Jest’s flexibility often offers ways to work around that.

In case this is helpful for anyone, we successfully mocked out LayoutAnimation using the code below

jest.mock("react-native/Libraries/LayoutAnimation/LayoutAnimation", () => ({
  ...require.requireActual(
    "react-native/Libraries/LayoutAnimation/LayoutAnimation"
  ),
  configureNext: jest.fn(),
}));

Though, none of the above worked for me, they really helped to find a solution. I was eventually able to properly mock the individual modules by following the implementation described in this blog post.

So, as @ide, suggested, I mocked the react-native interface. Using jest.mock() inside setup.js did not work for me, but instead I created a react-native.js file in tests/__mocks__ and added exports for the modules I need to mock:

import * as ReactNative from "react-native";

export const alert = jest.fn();
export const Alert = { alert };

export const dimensionWidth = 100;
export const Dimensions = {
  get: jest.fn().mockReturnValue({ width: dimensionWidth, height: 100 })
};

export const Image = "Image";

export const keyboardDismiss = jest.fn();
export const Keyboard = {
  dismiss: keyboardDismiss
};

export const Platform = {
  ...ReactNative.Platform,
  OS: "ios",
  Version: 123,
  isTesting: true,
  select: objs => objs["ios"]
};

export default Object.setPrototypeOf(
  {
    Alert,
    Dimensions,
    Image,
    Keyboard,
    Platform
  },
  ReactNative
);

Also, this allowed me to easily mock platform detection by simply overwriting the Platform.OS property inside a test (inspired by @tjbenton’s answer):

import { Platform } from "react-native";

it('renders Element if Android', () => {
  Platform.OS = 'android'
  ...
})

And, to check that the mocked methods are called as expected:

import { alert } from "react-native";

it("showAlert() calls Alert.alert", () => {
  showAlert();
  expect(alert).toHaveBeenCalled();
});

Hi all! After applying any and all of workarounds proposed here I still face the issue with these errors:

Test suite failed to run

    Invariant Violation: TurboModuleRegistry.getEnforcing(...): 'DeviceInfo' could not be found. Verify that a module by this name is registered in the native binary.

      at invariant (node_modules/invariant/invariant.js:40:15)
      at Object.getEnforcing (node_modules/react-native/Libraries/TurboModule/TurboModuleRegistry.js:39:3)
      at Object.<anonymous> (node_modules/react-native/Libraries/Utilities/NativeDeviceInfo.js:45:48)
      at Object.<anonymous> (node_modules/react-native/Libraries/Utilities/Dimensions.js:15:1)

Any clue how to resolve it?

I got same error as @0akl3y people think it’s just easy to mock react-native but it’s not! So please stop giving us those boilerplate answers.

I’ve found a workaround for this issue. You can add all Library subdirectories of react-native to the moduleDirectories prop in your jest.config.js. I’ve done this for our app with a recursive file-walker function:

// jest.config.js
const fs = require('fs')
const path = require('path')

function getFolderList(rootDir) {
  let files = fs.readdirSync(rootDir)
  files = files.map(file => {
    const filePath = path.join(rootDir, file)
    const stats = fs.statSync(filePath)
    if (stats.isDirectory()) return getFolderList(filePath)
    else if (stats.isFile()) return path.dirname(filePath)
  })

  const fileList = files.reduce((all, folderContents) => all.concat(folderContents), [])
  const folderList = fileList.filter((filePath, index) => fileList.indexOf(filePath) === index)
  return folderList
}

const moduleDirectories = [
  'node_modules',
  ...getFolderList(__dirname + '/node_modules/react-native/Libraries'),
]

module.exports = {
   moduleDirectories,
   // ... all your other jest.config.js configurations
}

After that, all mocks resolve correctly.

https://jestjs.io/docs/en/configuration#moduledirectories-array-string

I’m aware it’s definitely not the best solution and everyone should be aware that if they use it it could break at any time but for now it seems to be the only way to get the tests running on the new React Native version. We tried to mock all of React Native the way we’re currently mocking out Layout Animation but it didn’t seem to work properly…

If anyone manages to successfully create a mock it would be great if they could share it here.

@ccfz that does not work for me. Not sure if this is what you meant, but here is what I tried:

jest.mock('react-native', () => {
  const actualRn = jest.requireActual('react-native')
  return {
    ...actualRn,
    TouchableOpacity: () => 'TouchableOpacity',
  }
})

results in an error:

TypeError: (0 , _codegenNativeComponent.default) is not a function

I also tried it with require.requireActual(...) with the same result. So far the only viable solution seems to be importing the react-native modules via the full path (Which is -like mentioned before- not stable)

Thank you for looking into this

@ide That does not work for component getters, since it seems like those cannot be overwritten that way. However there is a way around this by using Object.defineProperty:

import React from 'react'
import renderer from 'react-test-renderer'

jest.mock('react-native', () => {
  const ReactNative = jest.requireActual('react-native')
  return Object.defineProperty(ReactNative, 'Button', {
    get: jest.fn(() => {
      return 'MockedButton'
    }),
  })
})

it('Mocks the component via react-test-renderer and correctly displays the mock in the snapshot', () => {
  const { Button } = require('react-native')
  const button = renderer.create(<Button title="it-also-works-in-snapshots" />)
  expect(button.mock).toBeDefined
  expect(button).toMatchSnapshot()
})

Test results:

 PASS  src/components/__tests__/example.test.js
  ✓ Mocks the component via react-test-renderer and correctly displays the mock in the snapshot (6ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 passed, 1 total
Time:        0.559s, estimated 1s
Ran all test suites matching /example.test/i.

Watch Usage: Press w to show more.


The snapshot also renders the mock correctly:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Mocks the component via react-test-renderer and correctly displays the mock in the snapshot 1`] = `
<MockedButton
  title="it-also-works-in-snapshots"
/>
`;


The following article pointed me in the correct direction: https://bambielli.com/til/2017-09-17-jest-doesnt-mock-getters/

However this seems like a workaround and requires much refactoring. I would prefer if it just worked like before. Publishing private implementation details seems to be the lesser evil than to lose the capability to conveniently mock those dependencies.

@ccfz You’d mock out react-native and re-export it with a different value for Animated. For example, mockReactNative = { ...originalReactNative, Animated: { whatever } }. Similar to how you’re mocking out just Animated.timing, you would mock out just ReactNative.Animated. Same thing for NativeModules, etc.

This is a working example with 0.61.2: https://github.com/facebook/react-native/issues/26579#issuecomment-538610849

@lroling8350 Mocking via the direct path is definitely a way to make this work. The problem with that is when RN changes the file path like @ide pointed out above. Checking if Keyboard’s dismiss was called can also be tested using a spy if someone just wants to do that.

However, I have more complicated mocks related to Animated and TextInput that require actual mocking. @fossage I ran into the same problem trying to mock out react-native. @ide could you maybe go into a little more detail about how something like jest.mock(‘Animated’) should be mocked with 0.61.1? It’s a common mock that requires an overriding aspect of Animated. Here is a sample mock I took from here, but we have very similar code in our company’s app.

jest.mock('Animated', () => {                                                                                                                                                                         
  const ActualAnimated = require.requireActual('Animated');                                                                                                                                           
  return {                                                                                                                                                                                            
    ...ActualAnimated,                                                                                                                                                                                
    timing: (value, config) => {                                                                                                                                                                      
      return {                                                                                                                                                                                        
        start: (callback) => {
          value.setValue(config.toValue);
          callback && callback()
        },                                                                                                                                                  
      };                                                                                                                                                                                              
    },                                                                                                                                                                                                
  };                                                                                                                                                                                                  
}); 

This seems to be the issue with the most details related to mocking in RN 0.61.1, @fossage maybe re-open the issue for now?

The easiest way I found to mock out the part that I needed was to just import it and overwrite it.

Previously I had this, and now it fails in 0.61
jest.mock('Animated', () => {
  const ActualAnimated = require.requireActual('Animated')
  return {
    ...ActualAnimated,
    timing: (value, config) => {
      return {
        start: (callback) => {
          value.setValue(config.toValue)
          callback?.({ finished: true })
        },
      }
    },
  }
})

To fix the issue in 0.61

import { Animated } from 'react-native'

Animated.timing = (value, config) => {
  return {
    start: (callback) => {
      value.setValue(config.toValue)
      callback?.({ finished: true })
    },
  }
}

@fossage Could you please show what you solution ended up being?

In my use case I want to check that Keyboard’s dismiss was called. So far I can just do:

jest.mock('Keyboard', () => ({
  dismiss: jest.fn(),
}));

Mocking out react-nativewould mean something like this right?

jest.mock('react-native', () => ({
  Keyboard: { dismiss: jest.fn() }
}));

which works to mock the Keyboard, but obviously the other react-native libraries, eg. Image are undefined now.

Did you add the other libraries to the mock? or what was your solution for this?

Mocking React Native’s interface did not work for me, as @altany suggested. I created a setup.js and added it to setupFilesAfterEnv config option:

jest.mock('react-native/Libraries/Utilities/Platform', () => ({
    OS: 'ios',
    select: jest.fn((selector) => selector.ios),
}));

Then, in each test, you can change Platform’s implementation like so:

import { Platform } from 'react-native';

test('your test', () => {
    Platform.select.mockImplementation((platforms) => platforms.android);
	Platform.OS = 'android';
	...
});

However there is a way around this by using Object.defineProperty:

This looks reasonable – due to the way RN has worked for a long time, Object.defineProperty is one of the few ways to override a getter (Object.setPrototypeOf({ Button: 'MockedButton' }, ReactNative) being another). I updated the code sample above so the examples are in one place.


Publishing private implementation details seems to be the lesser evil than to lose the capability to conveniently mock those dependencies.

For a given version of RN, code like this will work:

jest.doMock('react-native/Libraries/Components/Button', () => 'MockedButton');

It is a more brittle approach because of the coupling between your test and the internal path. If you understand it introduces coupling and can fix the tests if needed in the future, this approach might work for you. But in general with Jest, mocking the public interface you expect to interact with is a more robust approach and more likely to last.

@mksglu thanks! 🙇 🙏 I’ll take note of this approach and try to make it work with 0.63.4 on my end.

@SophieDonut Thank you for the example! A caveat with your solution is that the path to the library is a private implementation and therefore not stable. If RN changes the path to the libraries then you would have to update all your mocks. See @ide comment at the top.

This is intentional and you need to mock modules the same way as any other JS module now. You could in theory specify the path to the TextInput module, but the path is a private implementation detail that could change between releases. The logically correct approach is to code to the interface and mock out react-native.