React: `injectEnvironment` called twice: `react-test-renderer` and `react-dom`

Created on 31 Jul 2016  Â·  23Comments  Â·  Source: facebook/react

Trying to test a very simple React component using Material-UI through Jest and react-test-renderer, I'm stuck because ReactCompositeComponent: injectEnvironment() can only be called once.

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.

This may not really be a 'bug' in React or react-test-renderer per se, though one could imagine other dependencies somehow loading react-dom as well, causing the same issue.

I created a test-case at https://github.com/NicolasT/react-test-renderer-and-react-dom-incompatible which may provide some more context.

react: 15.3.0
react-dom: 15.3.0
react-test-renderer: 15.3.0

DOM Bug

Most helpful comment

Sure. Doesn't look like your change is in 15.3.1 but when 15.4 is released, this should be fixed.

All 23 comments

@NicolasT, I see the same issue. Seems like related to the change made in react @ 15.3.0, I downgrade react to 15.2.1 and the problem got solved. injectEnvironment() was called in 15.3.0 but not in 15.2.1.

This happens because the tool is assuming ReactDOM would be mocked out.
For example, with Jest, you could write:

jest.mock('react-dom');

to prevent this.

Ideally this shouldn’t be happening in the first place but there is more work to be done before we can avoid this.

any other solutions for this? Mocking react-dom is giving me another error. I believe bc enzyme uses it.

Maybe @cpojer could offer some insight. I think it should be possible to mock react-dom in one file but not the other, but it’s not clear to me from Jest docs what the “scope” of jest.mock is, and how to limit it to one file (or is it always limited?).

I think it's always limited to one file.

So I just looked into this a bit more. It seems that react-test-renderer and enzyme are not compatible due to this issue. In enzyme we do load react-dom and hook it up. Sure, jest could mock it, but other test frameworks might not be able to?

Additionally, i would assume people would want their snapshot tests next to their other tests. Currently that is not possible due to the above mentioned. Both renderers try to inject. Here's a type of test I would expect someone would want to write and couldn't at the moment.

import React from 'react';
import renderer from 'react-test-renderer';
import { mount } from 'enzyme';

class MyComponent extends React.Component {
  state = { active: true };

  render() {
    return (
      <div onClick={() => this.setState({active: !this.state.active}) }>
        Component is {this.state.active}
      </div>
   );
  }
}

describe('MyComponent', () => {
  it('matches snapshot', () => {
    const tree = renderer.create(<MyComponent />).toJSON();
    expect(tree).toMatchSnapshot();
  });

  it('updates state on click', () => {
    const wrapper = mount(<MyComponent />);
    wrapper.find('[onClick]').simulate('click');

    expect(wrapper).toHaveState('active', false);
  });
});

Is there anything I can do to help make this compatible? I'm just not sure what the solution you guys are looking for is.

Yes, this is a limitation right now; you can't use ReactDOM and ReactTestRenderer in the same file unfortunately.

Are there any plans to resolve that conflict or is it just something the community needs to be aware of? It seems like ReactTestRenderer would have to work with ReactComponentBrowserEnvironment or the injection invariant would have to be removed or skirted somehow (allowing environments to be replaced).

I would like to change core modules to be instantiable instead of relying on a global injection, and then you could share two in the same environment. We might also switch to copying renderer files to react-test-renderer so that the modules are totally independent and don't have shared state.

This bug also makes it difficult to write tests for components that use react-native-listener, which also relies on ReactDOM. Mocking out ReactDOM doesn't fix the problem and simply causes another error to be thrown.

@spicyj The builds in master should support this since they copy so should we close this out?

Sure. Doesn't look like your change is in 15.3.1 but when 15.4 is released, this should be fixed.

When is 15.4 scheduled to be released?

I would expect it to ship within two weeks but that is my guess.

I'd like to see https://github.com/facebook/react/pull/7649 land before the release, and it looks like it will happen. It provides a way to work around another annoying issue with test renderer.

Then https://github.com/facebook/react/issues/7482 needs to be fixed because it's a regression and we'll break a lot of projects if we ship without fixing it. I know @zpao planned to look into it.

I think that when both are fixed/merged, 15.4.0 should be good to go.

Thanks for the update!

@gaearon look like the 2 issues from your last comment are now fixed. Do you plan a release ? This will help us a lot.

Thanks for your time.

@rande we have an open ticket for the next release https://github.com/facebook/react/issues/7770. A RC for 15.4.0 should be out soon based on https://github.com/facebook/react/pull/7840#issuecomment-251249155.

You can try 15.4.0-rc.3. It should have this fix.

@gaearon thanks, the jest's tests are now green, which is a good news ;)

The hot reload component does not seem to work anymore: Module not found: Error: Cannot resolve module 'react/lib/ReactMount', found this https://github.com/gaearon/react-hot-loader/blob/master/docs/README.md#usage-with-external-react

I will check your documentation.

The hot reload component does not seem to work anymore

React Hot Loader 1.x has been deprecated and unsupported for about a year by now.

doesn't work in 16.0.2.

Any workarounds before it will be fixed?

import React from 'react'
import ReactDOM from 'react-dom'
import InputSwitch from './main'
import TestUtils from 'react-addons-test-utils'
import renderer from 'react-test-renderer'

test('default value comes from checked prop', () => {
  const component = renderer.create(
    <InputSwitch checked={true} onChange={() => true} />
  )
  expect(component.toJSON()).toMatchSnapshot()
})


test('onChange prop is triggered with checkbox value', () => {
  let onChange = jest.fn()
  const component = TestUtils.renderIntoDocument(
    <InputSwitch checked={true} onChange={onChange} />
  )
  TestUtils.Simulate.change(
    TestUtils.findRenderedDOMComponentWithTag(component, 'input'),
    {
      target: {checked: false}
    }
  )
  expect(onChange).toBeCalledWith(false)
})
 Test suite failed to run

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

      at invariant (node_modules/react/node_modules/fbjs/lib/invariant.js:38:15)
      at Object.injectEnvironment (node_modules/react/lib/ReactComponentEnvironment.js:36: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> (app/es6/components/input_switch/input_switch.test.js:5:52)
      at process._tickCallback (internal/process/next_tick.js:103:7)

This will be resolved in React 15.4.

This was fixed in React 15.4.0 which is out today.

Was this page helpful?
0 / 5 - 0 ratings