jest: Invalid hook call after `jest.resetModules` for dynamic `require`s

🐛 Bug Report

Invalid hook call when using jest.resetModules or jest.resetModuleRegistry with dynamic require in test

To Reproduce

Steps to reproduce the behavior: steps

Expected behavior

No invalid hook call

Link to repl or repo (highly encouraged)

demo repo

envinfo

  System:
    OS: macOS Mojave 10.14.6
    CPU: (8) x64 Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz
  Binaries:
    Node: 10.16.3 - ~/.nvm/versions/node/v10.16.3/bin/node
    Yarn: 1.17.3 - /usr/local/bin/yarn
    npm: 6.11.3 - ~/.nvm/versions/node/v10.16.3/bin/npm
  npmPackages:
    jest: 24.9.0 => 24.9.0

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 74
  • Comments: 20

Commits related to this issue

Most upvoted comments

I have also been running into this issue in a particularly nasty way. We use the resetModules config option globally, so tricks around calling jest.isolateModules() don’t work well. Instead, I came up with a workaround that globally excludes React from jest’s resetModules behavior.

In a file specified in the setupFiles config option, add the following:

let mockActualReact;

jest.doMock('react', () => {
  if (!mockActualReact) {
    mockActualReact = jest.requireActual('react');
  }
  return mockActualReact;
});

This sets up a mock for React that lazily loads the actual React, and always returns the result of the first initialization. This could also be generalized to implement a set of modules that are globally excluded from resetModules.

Generalized version:

const RESET_MODULE_EXCEPTIONS = [
  'react',
  'react-redux',
];

let mockActualRegistry = {};

RESET_MODULE_EXCEPTIONS.forEach(moduleName => {
  jest.doMock(moduleName, () => {
    if (!mockActualRegistry[moduleName]) {
      mockActualRegistry[moduleName] = jest.requireActual(moduleName);
    }
    return mockActualRegistry[moduleName];
  });
});

Personally, I’ve found this to be a useful construct, and now that the React hook issue is more prevalent, it might be good to have a version of this built in to Jest as something like a persistentModules or excludeFromResetModules option.

@kapral18 We are also experiencing the same issue. Have you found a work around for this?

The solution I came up with to our instance of this issue was more along the lines of solutions from Webpack and NPM: tell Jest to always use the same instance of React:

jest.config.json:

{
  "moduleNameMapper" : {
    "^react$": "<rootDir>/node_modules/react/index.js",
  }
}

I had the same issue, instead of making any changes in setup files I end up with using https://jestjs.io/docs/en/jest-object.html#jestisolatemodulesfn in order to isolate each module so they are imported without having cache.

ex.

import { render } from '@testing-library/react';
import React from 'react';

const { getPlatform } = require('device');
jest.mock('device');

describe('', () => {
	it('should render succesfully', () => {
		getPlatform.mockImplementation(() => 'desktop');
		jest.isolateModules(() => {
			const { SomeComponent } = require('./SomeComponent');
	
			const { container } = render(
				<SomeComponent>test</SomeComponent>
			);
	
			expect(container).toMatchSnapshot();
		});
	});
});

@lukevella Sorry for late response.

Here is what I came up with: Screen Shot 2019-11-12 at 17 37 08

Seems like if you wrap each jest.doMock() call into a jest.isolateModules() call in each test, - it actually re-mocks the modules, as opposed to basic behavior when the mocks are unchanged.

And when you need to “reset” the mock, you doMock it again but with jest.requireActual call.

The solution I came up with to our instance of this issue was more along the lines of solutions from Webpack and NPM: tell Jest to always use the same instance of React:

jest.config.json:

{
  "moduleNameMapper" : {
    "^react$": "<rootDir>/node_modules/react/index.js",
  }
}

Thx @bwhitty, require.resolve also works for me.

{
  "moduleNameMapper" : {
     "^react$": require.resolve("react")
  }
}

Thank you all so much. This was killing me all afternoon!

resetModules and isolateModules all caused issue for me. I just reinitialized the component to Test in the beforeEach state to get a fresh state:

    let page: JSX.Element;

    // Reinitialze element
    beforeEach(() => {
      page = (<ToTest/>);
    });;

   it('message', () => { 
     render(page);
   });

same issue on my side.

Doesn’t work for me. The first test is green, others are failing. Asking me to mock all hooks including useState;

    1. You might have mismatching versions of React and the renderer (such as React DOM)
    2. You might be breaking the Rules of Hooks
    3. You might have more than one copy of React in the same app
    See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.

      23 |
      24 | const Index = () => {
    > 25 |     const intl = useIntl();```

I am also facing the same issues when I import react component(using hooks inside the component) using require and jest.resetModules() in the test.