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(R) Core(TM) 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
jest.mock('View')
Describe what you expected to happen:
Jest should fail with an error telling you Cannot find module 'View' ...
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
.
@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-native
would 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?
@ccfz , unfortunately I was just assuming that I could get this working without having actually tried. I never was able to figure out a solution, specifically due to the problem you are describing.
The things I did try to no avail were:
moduleMapper
field in jest.config.js
to map names like Keyboard
to the location of the underlying libraries. This failed for numerous reasons.jest.mock('react-native', () => {
const actualRN = require.requireActual('react-native')
return {
...actualRN,
Keyboard: {
...
}
}
})
This failed because we would end up getting the actual version of RN rather than the mocked version created by the react-native
Jest preset(makes sense but I was curious to try). If you change the require.requireActual('react-native')
to just require('react-native')
, you end up in an endless loop(also makes sense but figured I would try anyway).
Ultimately I decided to fallback to RN 0.60.6 until some solution is found:(.
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.
@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?
Yes, @fossage please re-open this issue.
We too have not yet been able to upgrade to 0.61 because of the mocking issues.
This is how we mock analytics-react-native
as suggested here:
import {NativeModules} from 'react-native';
NativeModules.RNAnalytics = {};
const mockAnalytics = jest.genMockFromModule('@segment/analytics-react-native');
jest.mock('@segment/analytics-react-native', () => mockAnalytics);
Which raises the following error:
● Test suite failed to run
Failed to load Analytics native module.
at Object.<anonymous> (node_modules/@segment/analytics-react-native/build/cjs/bridge.js:6:11)
at Object.<anonymous> (node_modules/@segment/analytics-react-native/build/cjs/analytics.js:38:16)
at Object.<anonymous> (node_modules/@segment/analytics-react-native/build/cjs/index.js:3:19)
at Object.<anonymous> (jest/setup.js:22:26)
Same issue here nothing is working using latest rn 0.61.1
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(),
}));
@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.
Mocking something like the libraries and components is fine when you point to the component directly as above, but when trying to mock something like BackHandler or TouchableOpacity it seems to throw an issue. These mocks come from the @types folder and cant be mocked seemingly.
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.
We use jest.mock('NativeModules', () => { [...]
to mock only the native system code of a module. Mocking the entire module is not an option because we need to test the JS layer glue wrapping the native code.
Not being able to do such is preventing our upgrade to 0.61.x.
@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
@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
Have you been able to successfully mock the TouchableOpacity or the BackHandler? As I have not been able to just yet.
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.
@ide I think the issue here is that doing what you are proposing does not work. Doing so will lead to the error that @Oakl3y pointed to earlier. Many of us on the thread have tried it, and numerous other variations, and haven't come up with any viable solution. I still have a few things I would like to try, however I have a feeling that this is something that will need to be addressed by the RN team.
One of the main problems(aside from the error) about doing something like:
jest.mock('react-native', () => {
return {
...require.requireActual('react-native'),
[...specific module mocks]
}
})
is that the call to require.requireActual('react-native')
give us the unmocked React Native. Ideally what I think most of us would like to be able to do is use the mocked react native via the react-native
Jest preset and still have the ability to override specific modules with our own mocks.
@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();
});
$ 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.
@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.
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.
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
@fossage I think all the open questions have been answered and the issue can be closed.
Thanks again @ide for your detailed example that really helped.
@ide , thanks for the help!
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 })
},
}
}
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?
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?
Hi @G0retZ just the error message makes it hard to pinpoint the issue. Could you please show the mocking you did and or test. That way we can help you figure out what went wrong.
@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.
That was a good solution ;) cheers.
same issue here. does this issue caused by auto linking not applied to jest ?.. so what's the official solution...
@lgh06 check this solution from above.
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();
});
@ccfz for me,that solution( https://github.com/facebook/react-native/issues/26579#issuecomment-538610849 ) caused other issues:
https://github.com/oblador/react-native-vector-icons/issues/469
https://github.com/oblador/react-native-vector-icons/issues/1080
https://github.com/oblador/react-native-vector-icons/issues/1046
I removed all node_modules , cleaned npm cache, cleaned pod cache, then pod install.
instead of mocking View
/ Alert
, I have to mock in this way: jest.requireActual('react-native').View
jest.requireActual('react-native').Alert
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';
...
});
Most helpful comment
I was able to successfully mock the individual modules by mocking the direct path in react-native so items like
can become
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.