Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.61.0 - Jest "Cannot find module ..." #26579

Closed
fossage opened this issue Sep 25, 2019 · 37 comments
Closed

0.61.0 - Jest "Cannot find module ..." #26579

fossage opened this issue Sep 25, 2019 · 37 comments
Labels
Bug Resolution: Locked This issue was locked by the bot.

Comments

@fossage
Copy link

fossage commented Sep 25, 2019

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

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' ...

@fossage fossage added the Bug label Sep 25, 2019
@brunsy
Copy link

brunsy commented Sep 25, 2019

Similar to: #26447

@ide
Copy link
Contributor

ide commented Sep 25, 2019

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 fossage closed this as completed Sep 25, 2019
@ccfz
Copy link

ccfz commented Sep 26, 2019

@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?

@fossage
Copy link
Author

fossage commented Sep 26, 2019

@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:

  • Updating the moduleMapper field in jest.config.js to map names like Keyboard to the location of the underlying libraries. This failed for numerous reasons.
  • Doing something like:
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:(.

@lroling8350
Copy link

lroling8350 commented Sep 27, 2019

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.

@ccfz
Copy link

ccfz commented Sep 27, 2019

@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?

@alexpanov
Copy link

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)

@fossage fossage reopened this Sep 27, 2019
@maamounapprise
Copy link

Same issue here nothing is working using latest rn 0.61.1

@ghost
Copy link

ghost commented Oct 1, 2019

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(),
}));

@ccfz
Copy link

ccfz commented Oct 1, 2019

@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.

@Coffeegerm
Copy link

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.

@ghost
Copy link

ghost commented Oct 2, 2019

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.

@alpha0010
Copy link
Contributor

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.

@ide
Copy link
Contributor

ide commented Oct 3, 2019

@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: #26579 (comment)

@0akl3y
Copy link

0akl3y commented Oct 4, 2019

@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

@Coffeegerm
Copy link

Have you been able to successfully mock the TouchableOpacity or the BackHandler? As I have not been able to just yet.

@maamounapprise
Copy link

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.

@fossage
Copy link
Author

fossage commented Oct 4, 2019

@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.

@ide
Copy link
Contributor

ide commented Oct 5, 2019

@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.

@0akl3y
Copy link

0akl3y commented Oct 7, 2019

@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.

batpad added a commit to developmentseed/observe that referenced this issue Oct 7, 2019
@ide
Copy link
Contributor

ide commented Oct 7, 2019

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.

@meinto
Copy link

meinto commented Oct 8, 2019

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

@ccfz
Copy link

ccfz commented Oct 9, 2019

@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.

@tjbenton
Copy link

tjbenton commented Oct 25, 2019

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 })
    },
  }
}

@G0retZ
Copy link

G0retZ commented Nov 1, 2019

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?

@ccfz
Copy link

ccfz commented Nov 1, 2019

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.

@victorkurauchi
Copy link

@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.

@lgh06
Copy link

lgh06 commented Jan 21, 2020

same issue here. does this issue caused by auto linking not applied to jest ?.. so what's the official solution...

@ccfz
Copy link

ccfz commented Jan 21, 2020

@lgh06 check this solution from above.

@altany
Copy link

altany commented Jan 25, 2020

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();
});

@lgh06
Copy link

lgh06 commented Jan 31, 2020

@ccfz for me,that solution( #26579 (comment) ) caused other issues:

oblador/react-native-vector-icons#469
oblador/react-native-vector-icons#1080
oblador/react-native-vector-icons#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

@acostalima
Copy link

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';
	...
});

chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jul 7, 2020
The immediate need is to set up a way to mock the new `ZLPConstants`
in `NativeModules`, which we'll do in an upcoming commit.

Doing this, as recommended by React Native [1], also means we're
better prepared for the React Native v0.61 upgrade (zulip#3781), in which
Haste is removed [2]. A consequence of that removal, it seems, is
that mocks like this one, which we have now:

```
jest.mock('Linking', () => { ... }`
```

, won't work. Several people have handled this by changing 'Linking'
to something like 'react-native/Libraries/Linking/Linking', but this
is brittle because it couples our tests with the current directory
structure in 'react-native'. Better to do it this way.

We considered following the advice of others at that issue,
including a blog post [3] responding to the official suggestion with
an alternative. But we didn't reproduce the problems the post's
author mentioned, and we've so far been able to explain the hiccups
we've seen.

[1] facebook/react-native#26579 (comment)
[2] facebook/react-native#26579 (comment)
[3] facebook/react-native#26579 (comment)
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jul 8, 2020
Doing this, as recommended by React Native [1], means we're better
prepared for the React Native v0.61 upgrade (zulip#3781), in which Haste
is removed [2]. A consequence of that removal, it seems, is that
mocks like this one, which we have now:

```
jest.mock('Linking', () => { ... }`
```

, won't work. Several people have handled this by changing 'Linking'
to something like 'react-native/Libraries/Linking/Linking', but this
is brittle because it couples our tests with the current directory
structure in 'react-native'. Better to do it this way.

We considered following the advice of others at that issue,
including a blog post [3] responding to the official suggestion with
an alternative. But we didn't reproduce the problems the post's
author mentioned, and we've so far been able to explain the hiccups
we've seen.

[1] facebook/react-native#26579 (comment)
[2] facebook/react-native#26579 (comment)
[3] facebook/react-native#26579 (comment)
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jul 8, 2020
In the upcoming RN v0.60 -> v0.61 upgrade, Haste won't be used, so
the string "Linking" won't be resolved.

Move our "Linking" mock into our mock of 'react-native', added
in a recent commit, rather than pointing to
'react-native/Libraries/Linking/Linking' and thus coupling our tests
with the internal directory structure of `react-native`. [1]

[1]: See React Native's recommendation of this strategy, at
     facebook/react-native#26579 (comment).
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jul 8, 2020
The immediate need is to set up a way to mock the new `ZLPConstants`
in `NativeModules`, which we'll do in an upcoming commit.

Doing this, as recommended by React Native [1], also means we're
better prepared for the React Native v0.61 upgrade (zulip#3781), in which
Haste is removed [2]. A consequence of that removal, it seems, is
that mocks like this one, which we have now:

```
jest.mock('Linking', () => { ... }`
```

, won't work. Several people have handled this by changing 'Linking'
to something like 'react-native/Libraries/Linking/Linking', but this
is brittle because it couples our tests with the current directory
structure in 'react-native'. Better to do it this way.

We considered following the advice of others at that issue,
including a blog post [3] responding to the official suggestion with
an alternative. But we didn't reproduce the problems the post's
author mentioned, and we've so far been able to explain the hiccups
we've seen.

[1] facebook/react-native#26579 (comment)
[2] facebook/react-native#26579 (comment)
[3] facebook/react-native#26579 (comment)
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jul 10, 2020
Doing this, as recommended by React Native [1], means we're better
prepared for the React Native v0.61 upgrade (zulip#3781), in which Haste
is removed [2]. A consequence of that removal, it seems, is that
mocks like this one, which we have now:

```
jest.mock('Linking', () => { ... }`
```

, won't work. Several people have handled this by changing 'Linking'
to something like 'react-native/Libraries/Linking/Linking', but this
is brittle because it couples our tests with the current directory
structure in 'react-native'. Better to do it this way.

We considered following the advice of others at that issue,
including a blog post [3] responding to the official suggestion with
an alternative. But we didn't reproduce the problems the post's
author mentioned, and we've so far been able to explain the hiccups
we've seen.

[1] facebook/react-native#26579 (comment)
[2] facebook/react-native#26579 (comment)
[3] facebook/react-native#26579 (comment)
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jul 10, 2020
In the upcoming RN v0.60 -> v0.61 upgrade, Haste won't be used, so
the string "Linking" won't be resolved.

Move our "Linking" mock into our mock of 'react-native', added
in a recent commit, rather than pointing to
'react-native/Libraries/Linking/Linking' and thus coupling our tests
with the internal directory structure of `react-native`. [1]

[1]: See React Native's recommendation of this strategy, at
     facebook/react-native#26579 (comment).
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jul 11, 2020
With something called Haste, we were allowed to just pass "Linking"
to `jest.mock`, and it would automagically be known that we want
something called "Linking" from React Native.

With RN v0.61, Haste is no longer used, and that way of mocking
breaks.

One possible response is to spell out the entire path to "Linking"
within `react-native`:

jest.mock('react-native/Libraries/Linking/Linking')

. But that's brittle: that path may change with new React Native
versions, and it'll be unpleasant to have to adapt.

The recommended solution [1] is to mock the `react-native` module
ourselves, on top of the mocking that React Native's Jest setup does
for us. And to put our "Linking" mock there. So, do.

The *exact* recommendation is something that uses
`Object.setPrototypeOf`. We don't do that. Instead, Greg found an
earlier revision of the comment where that recommendation appears,
and go from there.

This way, we avoid an awkward problem with
`react-native-vector-icons`. That library re-exports `react-native`
in its `lib/react-native.js`, and imports that when they want
properties from the `react-native` module. Errors ensue; it appears
that their strategy cuts off access to properties we'd intended to
make available by using ReactNative as a prototype.

So, don't mess around with prototypes.

[1] facebook/react-native#26579 (comment)
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jul 11, 2020
With something called Haste, we were allowed to just pass "Linking"
to `jest.mock`, and it would automagically be known that we want
something called "Linking" from React Native.

With RN v0.61, Haste is no longer used, and that way of mocking
breaks.

One possible response is to spell out the entire path to "Linking"
within `react-native`:

jest.mock('react-native/Libraries/Linking/Linking')

. But that's brittle: that path may change with new React Native
versions, and it'll be unpleasant to have to adapt.

The recommended solution [1] is to mock the `react-native` module
ourselves, on top of the mocking that React Native's Jest setup does
for us. And to put our "Linking" mock there. So, do.

The *exact* recommendation is something that uses
`Object.setPrototypeOf`. We don't do that. Instead, Greg found an
earlier revision of the comment where that recommendation appears,
and go from there.

This way, we avoid an awkward problem with
`react-native-vector-icons`. That library re-exports `react-native`
in its `lib/react-native.js`, and imports that when they want
properties from the `react-native` module. Errors ensue; it appears
that their strategy cuts off access to properties we'd intended to
make available by using ReactNative as a prototype.

So, don't mess around with prototypes.

[1] facebook/react-native#26579 (comment)
gnprice pushed a commit to chrisbobbe/zulip-mobile that referenced this issue Jul 13, 2020
With something called Haste, we were allowed to just pass "Linking"
to `jest.mock`, and it would automagically be known that we want
something called "Linking" from React Native.

With RN v0.61, Haste is no longer used, and that way of mocking
breaks.

One possible response is to spell out the entire path to "Linking"
within `react-native`:

  jest.mock('react-native/Libraries/Linking/Linking')

But that's brittle: that path may change with new React Native
versions, and it'll be unpleasant to have to adapt.

The recommended solution [1] is to mock the `react-native` module
ourselves, on top of the mocking that React Native's Jest setup does
for us. And to put our "Linking" mock there. So, do.

The *exact* recommendation is something that uses
`Object.setPrototypeOf`. We don't do that. Instead, Greg found an
earlier revision of the comment where that recommendation appears,
and we go from there.

This way, we avoid an awkward problem with react-native-vector-icons.
That library re-exports `react-native` in its `lib/react-native.js`,
and imports that when they want properties from the `react-native`
module. Errors ensue; it appears that their strategy cuts off access
to properties we'd intended to make available by using ReactNative as
a prototype.

So, don't mess around with prototypes.

[1] facebook/react-native#26579 (comment)
@mksglu
Copy link

mksglu commented Feb 17, 2021

I tried all the solutions above in React Native version 0.63.4, but that does not work I want to changes the Platform's implementation in each test. cc @altany @acostalima

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';
	...
});

@acostalima
Copy link

@mksglu I'm yet to try the approach I suggested before in 0.63.4. Did you manage to find a solution meanwhile?

@mksglu
Copy link

mksglu commented Mar 26, 2021

Hello @acostalima, thank you for the response. I guess finally found a solution for 0.63.4. I created a new file in __mocks__ directory named is react-native.js and the file includes of following lines:

import * as ReactNative from 'react-native';

jest.doMock('react-native', () => {
  return Object.setPrototypeOf(
    {
      Dimensions: {
        get: () => ({width: 414, height: 896}),
      },
      Platform: {
        ...ReactNative.Platform,
        OS: 'ios',
        select: jest.fn((selector) => selector),
      },
      NativeModules: {
        ...ReactNative.NativeModules,
        SettingsManager: {
          settings: {
            AppleLocale: 'en_US',
          },
        },
        I18nManager: {
          localeIdentifier: 'en_US',
        },
      },
    },
    ReactNative,
  );
});

Then, when I create a test file like button.test.js:

import {Platform} from 'react-native';
describe('Button', () => {
  it('should call onPress function', () => {
    Platform.OS = 'android';
  });
});

@acostalima
Copy link

acostalima commented Mar 26, 2021

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

@facebook facebook locked as resolved and limited conversation to collaborators Oct 3, 2021
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Oct 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests