Enzyme: 3.4.3 breaks tests with "ShallowWrapper::setState() can only be called on root"

Created on 17 Aug 2018  Â·  6Comments  Â·  Source: enzymejs/enzyme

Describe the bug
Not sure why, but the 3.4.3 release broke my tests. I receive Error: ShallowWrapper::setState() can only be called on the root errors all over the place. Seems to be related to #1756.

The relevant test is attached. I was able to figure out that componentDidMount is called twice with the 3.4.3 release, which was not the case before.

To Reproduce

  it('<MyComponent />', async () => {
    fetch.mockResponse(JSON.stringify(jsonData));
    const wrapper = shallow(<MyComponent />, { disableLifecycleMethods: true });
    await wrapper.instance().componentDidMount();
    wrapper.update();
    expect(fetch.mock.calls.length).toEqual(1);
    expect(wrapper.find(Table).length).toEqual(1);
  });

The second call to componentDidMount has the following stack trace:

  console.error console.js:253
        at MyComponent.componentDidMount (/src/components/MyComponent.jsx:42:13)
        at /node_modules/enzyme/build/ShallowWrapper.js:204:20
        at Object.batchedUpdates (/node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:342:22)
        at new ShallowWrapper (/node_modules/enzyme/build/ShallowWrapper.js:203:24)
        at ShallowWrapper.wrap (/node_modules/enzyme/build/ShallowWrapper.js:1763:16)
        at ShallowWrapper.find (/node_modules/enzyme/build/ShallowWrapper.js:815:21)
        at ShallowWrapper.exists (/node_modules/enzyme/build/ShallowWrapper.js:1711:52)
        at Object._callee$ (/src/components/tests/MyComponent.spec.jsx:38:20)
        at tryCatch (/node_modules/regenerator-runtime/runtime.js:62:40)
        at Generator.invoke [as _invoke] (/node_modules/regenerator-runtime/runtime.js:296:22)
shallow bug

Most helpful comment

This will be fixed by #1768

All 6 comments

@monken Could you create a minimal test case? I can't try your code.

The following test is passed with v3.4.3, which is an expected behavior.

      it('should call `componentDidUpdate` when component’s `setState` is called2', () => {
        class Foo extends React.Component {
          constructor(props) {
            super(props);
            this.state = {
              foo: 'init',
            };
          }

          componentDidMount() {
            this.setState({ foo: 'update' });
          }

          render() {
            return <div>{this.state.foo}</div>;
          }
        }
        const spy = sinon.spy(Foo.prototype, 'componentDidMount');

        const wrapper = shallow(<Foo />, { disableLifecycleMethods: true });
        expect(spy).to.have.property('callCount', 0);
        wrapper.instance().componentDidMount();
        wrapper.update();
        expect(spy).to.have.property('callCount', 1);
      });

@monken can you share the code for MyComponent?

awaiting the result of componentDidMount seems strange, since react itself won't await it and it's not intended to be an async function.

import React, { Fragment, Component } from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';

class Table extends Component {
  render() {
    return (<table />);
  }
}

class MyComponent extends Component {

  state = {
    showTable: false,
  }

  componentDidMount() {
    this.setState({ showTable: true });
  }

  render() {
    const { showTable } = this.state;
    return (<Fragment>{showTable ? <Table /> : null}</Fragment>);
  }
}


describe('<MyContainer />', () => {
  it('should call `componentDidUpdate` when component’s `setState` is called', () => {
    const wrapper = shallow(<MyComponent />, { disableLifecycleMethods: true });
    expect(wrapper.find(Table).length).toEqual(0);
    wrapper.instance().componentDidMount();
    wrapper.update();
    expect(wrapper.find(Table).length).toEqual(1);
  });
});

Output:

 FAIL  src/components/tests/foo.spec.jsx
  <MyContainer />
    ✕ should call `componentDidUpdate` when component’s `setState` is called (11ms)

  ● <MyContainer /> › should call `componentDidUpdate` when component’s `setState` is called

    ShallowWrapper::setState() can only be called on the root

This will be fixed by #1768

When is this going to npm?

@koba04 thanks for the quick turn-around on this!

Was this page helpful?
0 / 5 - 0 ratings