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)
@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!
Most helpful comment
This will be fixed by #1768