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

Invariant Violation: ReactCompositeComponent: injectEnvironment() can only be called once. #1353

Closed
moimael opened this issue Aug 2, 2016 · 54 comments · Fixed by grommet/grommet-cli#33

Comments

@moimael
Copy link

moimael commented Aug 2, 2016

Note from Maintainers

The bug is in React, and it will be fixed in React 15.4.0.
Until React 15.4.0 is out, update all React packages to 15.4.0-rc.4 if you want to try it out.


We get this error a lot when trying to test our components.

Not sure where this is coming from.

● Runtime Error
  - Invariant Violation: ReactCompositeComponent: injectEnvironment() can only be called once.
        at invariant (node_modules/fbjs/lib/invariant.js:38:15)
        at Object.ReactComponentEnvironment.injection.injectEnvironment (node_modules/react/lib/ReactComponentEnvironment.js:43:60)
        at Object.<anonymous> (node_modules/react/lib/ReactTestRenderer.js:130:37)
        at Object.<anonymous> (node_modules/react-test-renderer/index.js:4:18)
        at Object.<anonymous> (src/shared/components/burger-menu/__tests__/burger-menu.test.js:3:52)
        at handle (node_modules/worker-farm/lib/child/index.js:41:8)
        at process.<anonymous> (node_modules/worker-farm/lib/child/index.js:47:3)
        at emitTwo (events.js:87:13)
        at process.emit (events.js:172:7)
        at handleMessage (internal/child_process.js:695:10)
        at Pipe.channel.onread (internal/child_process.js:440:11)
@cpojer
Copy link
Member

cpojer commented Aug 3, 2016

This should be fixed with jest-react-native 14.1.1.

@cpojer cpojer closed this as completed Aug 3, 2016
@moimael
Copy link
Author

moimael commented Aug 3, 2016

I'm not doing any react-native stuff, and I'm not using jest-react-native. Will it be fixed for normal React too ?

@cpojer cpojer reopened this Aug 3, 2016
@cpojer
Copy link
Member

cpojer commented Aug 3, 2016

I see, it must be react-dom then. Can you try jest.mock('react/lib/ReactDefaultInjection') and see if that makes it work?

@moimael
Copy link
Author

moimael commented Aug 3, 2016

Now it give me this error :

Invariant Violation: getNodeFromInstance: Invalid argument.

@cpojer
Copy link
Member

cpojer commented Aug 3, 2016

Can you share a repository on GitHub that highlights this error? I'm pretty sure we need to either provide better mocking defaults in Jest or figure out a way for React to not completely blow up when react-dom and react-test-renderer are both required. The problem here isn't a Jest issue, it's that react-dom and the react-test-renderer are both rendering targets for React and right now in React you can only load a single rendering target.

@moimael
Copy link
Author

moimael commented Aug 3, 2016

So I was wondering, since none of my component import react-dom, why it would cause the issue. And actually the issue come from an HOC I use : https://github.com/d6u/react-container-query which do import react-dom. And every component who throws this error is wrapped in the applyContainerQuery HOC. I cannot really share repo since it's on our private Github repo but just doing a really simple test with a component wrapped in this HOC would throw the error.

@njpatel
Copy link

njpatel commented Aug 5, 2016

facebook/react#7386 references the issue too. The submitter provided a test case: https://github.com/NicolasT/react-test-renderer-and-react-dom-incompatible .

It looks like react-test-renderer calls injectEnvironment, but so does react-dom, which is (in my actual application) imported somewhere within a Material-UI module which is in turn imported by my component code.

@LouisStAmour
Copy link

LouisStAmour commented Aug 7, 2016

I'm also seeing this caused by https://github.com/react-bootstrap/react-bootstrap/blob/master/src/Modal.js#L7 when I used a bootstrap Modal in my code. Running jest.mock('react/lib/ReactDefaultInjection') "fixed" that error, but when I open/display the modal, I see:

  - TypeError: Cannot read property 'monitorScrollValue' of null
        at Object._assign.ensureScrollValueMonitoring (node_modules/react/lib/ReactBrowserEventEmitter.js:310:50)
        at Object.ReactMount._renderNewRootComponent (node_modules/react/lib/ReactMount.js:278:30)
        at Object.ReactMount._renderSubtreeIntoContainer (node_modules/react/lib/ReactMount.js:371:32)
        at Object.ReactMount.renderSubtreeIntoContainer [as unstable_renderSubtreeIntoContainer] (node_modules/react/lib/ReactMount.js:313:23)
        at _renderOverlay (node_modules/react-overlays/lib/Portal.js:84:50)
        at componentDidMount (node_modules/react-overlays/lib/Portal.js:47:10)
        at invokeComponentDidMountWithTimer (node_modules/react/lib/ReactCompositeComponent.js:60:18)
...

I tried avoiding this by adding jest.mock('react/lib/ReactBrowserEventEmitter'); but it doesn't help.

TypeError: Can't add property _hostNode, object is not extensible
        at Object.precacheNode (node_modules/react/lib/ReactDOMComponentTree.js:47:22)
        at Object.ReactMount._mountImageIntoNode (node_modules/react/lib/ReactMount.js:487:29)
        at mountComponentIntoNode (node_modules/react/lib/ReactMount.js:112:14)
        at ReactTestReconcileTransaction.Mixin.perform (node_modules/react/lib/Transaction.js:138:20)
        at batchedMountComponentIntoNode (node_modules/react/lib/ReactMount.js:126:15)
        at Object.ReactDefaultBatchingStrategy.batchedUpdates (node_modules/react/lib/ReactDefaultBatchingStrategy.js:61:7)
        at Object.batchedUpdates (node_modules/react/lib/ReactUpdates.js:98:20)
        at Object.ReactMount._renderNewRootComponent (node_modules/react/lib/ReactMount.js:285:18)
        at Object.ReactMount._renderSubtreeIntoContainer (node_modules/react/lib/ReactMount.js:371:32)
        at Object.ReactMount.renderSubtreeIntoContainer [as unstable_renderSubtreeIntoContainer] (node_modules/react/lib/ReactMount.js:313:23)
        at _renderOverlay (node_modules/react-overlays/lib/Portal.js:84:50)
        at componentDidMount (node_modules/react-overlays/lib/Portal.js:47:10)
...

The problem ultimately stems from https://github.com/react-bootstrap/react-overlays/blob/master/src/Portal.js#L74 (which is line 84 in the above stacktrace) called by https://github.com/react-bootstrap/react-overlays/blob/master/src/Portal.js#L28 (aka line 47)

If I try mocking react/lib/ReactMount, I get:

  - TypeError: Cannot read property 'contains' of undefined
        at node_modules/dom-helpers/query/contains.js:8:19
        at focus (node_modules/react-overlays/lib/Modal.js:446:58)
        at onShow (node_modules/react-overlays/lib/Modal.js:388:10)
        at componentDidMount (node_modules/react-overlays/lib/Modal.js:354:12)
...

Which is from the contains function call at https://github.com/react-bootstrap/react-overlays/blob/master/src/Modal.js#L414

@ffxsam
Copy link

ffxsam commented Aug 7, 2016

I'm getting this as well, when using Jest on a project that uses React and Material UI.

@ffxsam
Copy link

ffxsam commented Aug 7, 2016

Here are the only two tests I have:

Link.test.js

import React from 'react';
import renderer from 'react/lib/ReactTestRenderer';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import Link from '../imports/common-ui/components/Link';

function handleClick() {

}

test('renders some text with an onClick handler', () => {
  const component = renderer.create(
    <MuiThemeProvider muiTheme={getMuiTheme()}>
      <Link onClick={handleClick}>Click here</Link>
    </MuiThemeProvider>
  );
  let tree = component.toJSON();

  expect(tree).toMatchSnapshot();
});

ArtistTrackUI.test.js

import React from 'react';
import renderer from 'react/lib/ReactTestRenderer';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import ArtistTrackUI from
  '../imports/common-ui/components/Tracks/ArtistTrackUI';

function userAction() {}

test('renders', () => {
  const component = renderer.create(
    <MuiThemeProvider muiTheme={getMuiTheme()}>
      <ArtistTrackUI
        duration={120}
        name="Some Track"
        onUserAction={userAction}
        playing={false}
        position={20}
      />
    </MuiThemeProvider>
  );
  let tree = component.toJSON();

  expect(tree).toMatchSnapshot();
});

@LouisStAmour
Copy link

Workaround: By using jest.mock('react/lib/ReactDefaultInjection'); with automock turned off, I'm able to get everything running smoothly for tests which don't actually need react-dom. (We get past the load errors.)

For tests that require react-dom, I've duplicated my setup code but replaced import renderer from 'react-test-renderer'; with import { mount } from 'enzyme'; import jasmineEnzyme from 'jasmine-enzyme'; and then with slight modifications -- renaming renderer.create to mount, changing component.toJSON() to component.html(), etc. I'm able to get things up and running. See Enzyme's Full Render API and jasmine-enzyme, which I loaded using the suggested non-Jest beforeEach filter instead of using the Jest-suggested setupTestFrameworkScriptFile so I could run both render functions in different Jest test files at the same time.

@gaearon
Copy link
Contributor

gaearon commented Aug 7, 2016

jest.mock('react-dom') should be enough for this.

@gaearon
Copy link
Contributor

gaearon commented Aug 7, 2016

(I’m not saying it’s good though—this requirement is confusing and should ideally be fixed, or at least documented, on the React side.)

@LouisStAmour
Copy link

LouisStAmour commented Aug 7, 2016

With jest.mock('react-dom') I still get the following error, same as when I mock both react/lib/ReactDefaultInjection and react/lib/ReactMount

  - TypeError: Cannot read property 'contains' of undefined
        at node_modules/dom-helpers/query/contains.js:8:19
        at focus (node_modules/react-overlays/lib/Modal.js:446:58)
        at onShow (node_modules/react-overlays/lib/Modal.js:388:10)
        at componentDidMount (node_modules/react-overlays/lib/Modal.js:354:12)
        at invokeComponentDidMountWithTimer (node_modules/react/lib/ReactCompositeComponent.js:60:18)
...

Edit: Enzyme's mount "just works" except when my code is asking for layout dimensions of obviously unrendered components. Further mocking seems to be required in each case.

@gaearon
Copy link
Contributor

gaearon commented Aug 7, 2016

With Enzyme's mount, "it just works".

Enzyme’s mount() uses jsdom which is a complete re-implementation of DOM in memory 😄 . I don’t think it’s entirely surprising that it “just works” for this use case compared to a utility that has barely been out for a week, and has explicit goal of not implementing DOM-specific methods.

I still get the following error, same as when I mock both react/lib/ReactDefaultInjection and react/lib/ReactMount

If you look at the stack trace, you will see that it fails because it calls DOM APIs (namely node.contains()). As I suggested in facebook/react#7371 (comment), the best we can do is to provide a mock object that has DOM node APIs but doesn’t actually do anything. This is not currently implemented but your ideas are welcome too!

@gaearon
Copy link
Contributor

gaearon commented Aug 7, 2016

Oh, and another workaround in the meantime would be to mock the component that uses those DOM APIs (in your case, node_modules/react-overlays/lib/Modal.js).

@LouisStAmour
Copy link

Thanks @gaearon - I tried the mock route before commenting. I'm not sure what I'm doing wrong, I've tried mocking jest.mock('dom-helpers');, jest.setMock('dom-helpers/util/inDOM', false); and creating a top-level __mocks__ folder to mock Modal's focus function to a noop. But every time it keeps loading ./node_modules/dom-helpers/query/contains.js with inDOM returning true, so my mocks aren't taking effect. Very strange, because jest.mock('react-dom'); worked, as did a mock for a local file. I wonder if it's somehow pre-loaded, or if a hook gets lost somewhere. When I place a breakpoint in dom-helpers/query/contains.js, I can run jest.setMock in the REPL and it works exactly as expected, with a require before returning true for inDOM before and false afterward. Any suggestions for debugging requires-mocking?

@LouisStAmour
Copy link

I was able to get it working thanks to extensive use of a debugger and conditional breakpoints, with the following line. No idea why it wasn't working before, maybe it's because I'm using ES6 function shorthand, or maybe it was the lack of .js at the end in my last comment's examples.

jest.mock('dom-helpers/util/inDOM.js', ()=>false);

As to the rest, it appears I'm using too much state and not enough props for me to interact with the shallow rendered output of toJSON(), if I understand this correctly.

@bsr203
Copy link

bsr203 commented Aug 9, 2016

Hi @cpojer 's solution works for me
jest.mock('react/lib/ReactDefaultInjection');
but, I wonder why I get this error if I don't use ReactDOM or enzyme explicitly. I have very similar test as in the CRA and I don't see any mocking there. IS there a way to trace where all the registration happening.

@gaearon
Copy link
Contributor

gaearon commented Aug 9, 2016

I wonder why I get this error if I don't use ReactDOM or enzyme explicitly.

Some component you use imports react-dom (e.g. for findDOMNode).

@cpojer
Copy link
Member

cpojer commented Aug 10, 2016

Yeah we should really come up with documentation for this and maybe even add error messages to React that point you to what you need to do to avoid the errors.

@warent
Copy link

warent commented Aug 15, 2016

I just ran into the same issue, and a search brought me to @cpojer's jest.mock('react/lib/ReactDefaultInjection'); solution which worked for me as well

@cpojer
Copy link
Member

cpojer commented Aug 18, 2016

There is a workaround and the next version of React won't have this problem.

@cpojer cpojer closed this as completed Aug 18, 2016
@gaearon
Copy link
Contributor

gaearon commented Aug 18, 2016

(For future readers: probably not 15.3.1 but 15.4.0 might have this fix.)

@blairanderson
Copy link

confirming 15.3.1 still has this problem

@cpojer
Copy link
Member

cpojer commented Sep 20, 2016

I don't see the value in that. Once the test-renderer has a find API there is little value in using enzyme for this kind of testing. The React team is working on it.

@tleunen
Copy link

tleunen commented Sep 20, 2016

But the test-renderer doesn't have any way to simulate a click, the only way is to call the props. But what if a component is handling a click on an element to update the internal state and change the rendering based on that?
Unless you know they're also working on a way to click on something? :)

@cpojer
Copy link
Member

cpojer commented Sep 20, 2016

What is a click in an environment that is a fake implementation of the DOM? It is simply a function call, so calling a prop called onClick is exactly the same.

@shanecav
Copy link

If anyone else is trying to do Jest snapshots and enzyme tests in the same file, I started using enzyme-to-json as an alternative to react-test-renderer and it's been working great. (@tleunen)

@countoren
Copy link

countoren commented Sep 27, 2016

just cloned a fresh copy, after npm i running npm test got
injectEnvironment() can only be called once.
in all competents tests
from the ReactTestRenderer import sentences .
Any help?

@gaearon
Copy link
Contributor

gaearon commented Sep 27, 2016

@countoren

The problem will be fixed in React 15.4.0, but it is not released yet.
I believe this is mentioned in the thread above so feel free to read through it!

@royriojas
Copy link

royriojas commented Oct 18, 2016

@shanecav thanks for your suggestion!

just in case anyone is blocked by this issue. enzyme-to-json as an alternative to react-test-renderer works like a charm!

@gaearon
Copy link
Contributor

gaearon commented Oct 19, 2016

You should be able to try React 15.4.0-rc.4 (be sure to bump all packages).
If you have issues please report them in facebook/react#7770.

@DarrylD
Copy link

DarrylD commented Nov 10, 2016

Using 17.0.0 and still got the error. Switched over to enzyme-to-json and it's working fine.

@gaearon
Copy link
Contributor

gaearon commented Nov 10, 2016

@DarrylD

Have you had a chance to read my comments above?

From #1353 (comment):

You should be able to try React 15.4.0-rc.4 (be sure to bump all packages).

From #1353 (comment):

The problem will be fixed in React 15.4.0, but it is not released yet.

It is not expected to be fixed in Jest 17 because it is not a bug in Jest.

The bug is in React, and it will be fixed in React 15.4.0.
You can already install React 15.4.0-rc.4 if you want to try it out.

Cheers!

@DarrylD
Copy link

DarrylD commented Nov 10, 2016

@gaearon Skimmed over the most important comment 😑. Sorry about that.

@gaearon
Copy link
Contributor

gaearon commented Nov 10, 2016

No worries. Let me add this to the top post so less people miss it.

@matheus208
Copy link

matheus208 commented Nov 14, 2016

After updating to react 15.4.0-rc.4 I get this when I run my test:

 Cannot find module 'react/lib/ReactTestRenderer' from 'index.js'

      at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:142:17)
      at Object.<anonymous> (node_modules/react-test-renderer/index.js:4:18)
      at Object.<anonymous> (__test__/src/components/article/article.test.js:2:52)

My test is quite simple:

import React from 'react';
import renderer from 'react-test-renderer';
import Article from '../../../../src/components/article';


describe('article', () => {

  it('renders', () => {
    const tree = renderer.create(
      <Article
        hasPadding={true}
        newsId={1} />
    ).toJSON();

    expect(tree).toMatchSnapshot();
  });
});

@sheepsteak
Copy link
Contributor

@matheus208 you need to use the new version of react-test-renderer too (set as next tag like React).

vincentaudebert added a commit to springload/springtunes that referenced this issue Nov 15, 2016
@gaearon
Copy link
Contributor

gaearon commented Nov 16, 2016

This was fixed in React 15.4.0 which is out today.

@kaiomagalhaes
Copy link

Hey @gaearon. I'm facing this same issue when I try to mount a component using react 15.6. I'm getting the message:

    console.error node_modules/fbjs/lib/warning.js:36
      Warning: Unknown props `mobile`, `precision` on <Test> tag. Remove these props from the element. For details, see https://fb.me/react-unknown-prop
          in Test (at index.js:21)
          in div (at index.js:18)
          in NumericInput (created by WrapperComponent)
          in WrapperComponent

When I run the mount on the following test:

import React from 'react';
import renderer from 'react-test-renderer';
import { mount, shallow } from 'enzyme';
import NumericInput from '../index';

jest.mock('react-numeric-input', () => 'Test');

describe('NumericInput', () => {
  it('renders without crashing', () => {
    mount(<NumericInput isMax />);
  });
});

with the component:

import PropTypes from 'prop-types';
import React from 'react';
import cx from 'classnames';
import ReactNumericInput from 'react-numeric-input';

import styles from './NumericInput.css';

const propTypes = {
  value: PropTypes.number,
  isMax: PropTypes.bool.isRequired,
};

const defaultProps = {
  value: 0,
};

const NumericInput = ({ value, isMax }) => (
  <div>
    <span className={cx(styles.label)} > {isMax ? 'Max' : 'Min'} </span>

    <ReactNumericInput
      className={cx(styles['numeric-input'])}
      mobile={false}
      value={value}
      format={num => (`${num} RSF`)}
      precision={3}
      size={10}
    />
  </div>
);

NumericInput.propTypes = propTypes;
NumericInput.defaultProps = defaultProps;

export default NumericInput;

React version: "react": "^15.6.1"
Jest: "jest": "^20.0.4"
Enzyme: "enzyme": "^2.9.1",

@gaearon
Copy link
Contributor

gaearon commented Jul 12, 2017

@kaiomagalhaes Why is this the same issue? The message in #1353 (comment) looks very different to me.

In your case I think using jest.mock('react-numeric-input', () => 'react-numeric-input'); could work around the issue since it wouldn’t check for valid DOM nodes on what appears to be a custom component. In React 16 we’ll likely remove this warning anyway so it shouldn’t be a problem.

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.