
TL;DR: React Native 0.61 removed Haste, which broke a lot of Jest mocks. This post explains why filename-based mocks stopped working and shows a more robust way to mock react-native modules.
Why mocks broke in React Native 0.61
A few months after React Native 0.61 came out, I tried to run our tests. They didn't work.
Before 0.61, React Native used Haste to map module files by filename. This made mocking simple:
jest.mock("Button", () => {});
React Native 0.61 dropped Haste. That meant filename-based mocks stopped working entirely, and we started seeing errors like:
Cannot find module 'Button'
Two ways to mock modules
The React team recommendstwo approaches.
1. Specify the full path:
jest.mock("react-native/Libraries/Components/Button", () => {});
This works for individual modules, but it's brittle - it couples your tests to internal paths that can change between versions. I also couldn't get it to work reliably across all cases.
2. Mock the react-native interface
This is more robust. The idea is to mock react-native itself in your setup.js:
import * as ReactNative from "react-native";
jest.doMock("react-native", () => {
return Object.setPrototypeOf(
{
Button: "Button",
LayoutAnimation: {
...ReactNative.LayoutAnimation,
configureNext: jest.fn(),
},
Platform: {
...ReactNative.Platform,
OS: "ios",
Version: 123,
isTesting: true,
select: (objs) => objs["ios"],
},
NativeModules: {
...ReactNative.NativeModules,
Override: { great: "success" },
},
},
ReactNative
);
});
I had issues with this approach too. When I tried to access Platform.OS I'd get:
Couldn't read OS of undefined
What actually worked
What worked for me was mocking react-native manually inside a tests/__mocks__/react-native.js file:
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
);
I added exports for every react-native module imported in tested files, one by one. For anything that needed a mock function, I exported the mock separately so I could assert on it:
import { alert } from "react-native";
it("showAlert() calls Alert.alert", () => {
showAlert();
expect(alert).toHaveBeenCalled();
});
Mocking platform detection
One more problem: I needed to test platform-specific code for iOS, Android, and web.
Before 0.61 we'd been using this:
export const mockPlatform = (OS) => {
jest.resetModules();
jest.doMock("Platform", () => ({ OS, select: (objs) => objs[OS] }));
};
After 0.61 this also stopped working. The fix turned out to be much simpler: just overwrite Platform.OS directly inside the test:
import { Platform } from "react-native";
it("renders Element if Android", () => {
Platform.OS = "android";
renderIfAndroid();
expect(wrapper.find(Element).exists()).toBe(true);
});
That's it. With this setup we were finally able to mock everything properly and move forward with the 0.61 upgrade.
Key takeaways
Haste removal in 0.61 breaks all filename-based Jest mocks. Full module paths work but are brittle. Mocking the react-native interface in __mocks__ is more robust and easier to maintain. And for platform detection, just overwrite Platform.OS directly in the test.
Looking for more on React Native in production? Check out my conference talks:
Written by Tania Papazafeiropoulou - Senior Front-End Engineer & Tech Lead.