When using shallow, the .setProps() method doesn't trigger the componentDidUpdate lifecycle method. However, .setState() does trigger it. It seems like they both should (or both shouldn't).
describe('MyComponent', () => {
let spy;
before(function() {
spy = sinon.spy(MyComponent.prototype, 'componentDidUpdate');
});
after(function() {
spy.restore();
});
// fails
it('calls componentDidUpdate when setting props', () => {
const wrapper = shallow(<MyComponent color="yellow" />);
wrapper.setProps({ 'color': 'green' });
expect(spy.calledOnce).to.be.true;
});
// passes
it('calls componentDidUpdate when setting state', () => {
const wrapper = shallow(<MyComponent color="yellow" />);
wrapper.setState({ foo: 'bar' });
expect(spy.calledOnce).to.be.true;
});
});
FWIW, setProps is being deprecated.
https://github.com/facebook/react/pull/5588/files
http://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#new-deprecations-introduced-with-a-warning
@blainekasten thanks for that info. Funny - I didn't even know .setProps() existed as a real API.
The deprecation doesn't affect us in this case, since .setProps() is achieved using setState() of a parent wrapper component, anyway.
At the moment I'm doing
const prevProp = wrapper.props()
wrapper.setProps({
current_step: 2,
})
// forces an update call to componentDidUpdate - https://github.com/airbnb/enzyme/issues/34
wrapper.instance().componentDidUpdate(prevProp)
+1, how correctly to solve the problem?)
I'm betting this will be resolved by https://github.com/airbnb/enzyme/pull/318
I'm also performing similar hacks to what @CurtisHumphrey mentioned above.
I wonder what the preferred way of achieving this is, if not with setProps? I have yet to dig in too deep myself, but this is something I am definitely interested in contributing to. It looks like https://github.com/airbnb/enzyme/pull/318 is on a good track.
hi @blainekasten I'm using the latest release 2.4.1 and it still hasn't fixed the issue. I ended up having to use mount to trigger the componentDidUpdate. Would love to just use shallow instead
@bricejlin
lifecycleExperimental flag.You can do with the flag for enabling all lifecycle methods in shallow.
shallow(<Foo />, { lifecycleExperimental: true })
Closing since this should be resolved by using lifecycleExperimental flag.
In react-native lifecycleExperimental works for componentDidMount() but not for componentDidUpdate().
"name": "enzyme",
"version": "3.1.0",
"react": "16.0.0",
"react-native": "0.47.2",
FWIW, I think @CurtisHumphrey 's approach is closest to the best approach. Why do people find it necessary to test if React fires a lifecycle event? If React fails to go through lifecycle events, then React has a larger issue. Testing if react fires a lifecycle event when a prop updates is pointless. The whole point of React is to "react" to prop and state changes. It's like testing to make sure image.onload, or window.onload fires in a browser. If these built-in browser events fail to fire in Chrome when they are supposed to fire, then Google is out of business the next day.
I think in general, people over-test things that don't need to be tested. I also think that people forget that your React components are 90% vanilla javascript.
You don't always need enzyme. React is just javascript. If your ES6 class constructor looks like this...
constructor(props) {
super(props);
// other stuff here...
}
Then you can test if componentDidUpdate works correctly, without having to test if React fires it when it's supposed to fire it.
describe('componentDidUpdate()', () => {
it('should do something', () => {
const prevProps = { // define previous props };
const prevState = { // define previous state if needed };
const instance = new MyComponent({ // new props go here });
instance.state = { // if you need a new set state, set it here };
// some method called by componetDidUpdate when there is an update.
instance.someMethod = jest.fn();
instance.componentDidUpate(prevProps, prevState);
// expect stuff here.
expect(instance.someMethod).toHaveBeenCalledWith( stuff );
})
})
This is what is called "unit testing" a class method. Enzyme shallow() is not a unit test. It is an integration test. Enzyme shallow() mounts a shallow version of a component in a virtual browser.
A true unit test only focuses on a function or method.
The test above tests the inputs of the componentDidUpdate() class method, tests the expected side effects of the class method, and does all of this without redundantly testing if React works.
I like Enzyme, but it is not always the answer.
@mrbinky3000 First, you should be using testing a component the way it's used - new MyComponent is never how it's used in production, so that test is not very valuable.
As for "integration test", everything should be an integration test - if it's not one, then your test has to have knowledge about the implementation under test, which is coupling, and makes your tests more brittle.
Enzyme certainly isn't always the answer, but setting up a contrived scenario to test something in an environment in which it never runs in production gives you nothing but false confidence.
Sorry if this was off-topic. I can discuss this further offline if you want. I've been thinking about this for a while now and would like to know more about why you think this is bad. But here are some things to consider.
1) All tests are contrived.
2) If you properly unit test all the class methods and/or functions for a given Component, then the sum of your units should be ok. Garbage in garbage out, and vice versa.
3) You still need integration tests like shallow and mount. For example, I still use them to test any conditional statements in a render() method. My argument is people need a fraction of the shallow and mount tests that they create today.
The sum of your unit tests is not equal to integration tests. You might be able to “get by” with more focused unit tests and fewer wrappers, but those aren’t better tests - those are more contrived since, unlike the wrappers, they don’t mimic production usage.
Solved it by using
wrapper.setProps({
user: {
test: {error: "test"}
}
});
wrapper.find('.smth').simulate('click');
Works as a charm, setprops are set, you just need to call a function again that triggers the component o re-render, it will trigger the componentDidUpdate function
The latest update seems to broke this again.
node_modules/enzyme/build/ShallowWrapper.js
line 552
if (lifecycles.componentDidUpdate && typeof instance.componentDidUpdate === 'function' && (!state || (0, _Utils.shallowEqual)(state, _this3.instance().state))) {
instance.componentDidUpdate(prevProps, state, snapshot);
}
The last condition make it that only state change will trigger instance.componentUpdate,
@LiangMingChen could you file a new issue about this, and fill in the entire issue template? That would really help it get fixed quickly.
@ljharb @LiangMingChen I'm encountering that as well, was the issue created ?
Nope, please file one :-)
Nope, please file one :-)
Turns out it works sorry
Sorry to bring an old thread back up, but I'm running into a similar issue detailed here, and I can't seem to find any other solutions when searching. Here's my test:
it('should reset gridWrapperNode scrollTop position if data has changed', () => {
const mounted = mount(<Grid {...props} />);
const gridNode = document.createElement('div');
const gridWrapper = document.createElement('div');
gridWrapper.classList.add('datagrid-grid-wrapper');
gridNode.appendChild(gridWrapper);
mounted.instance().gridNode = gridNode;
gridWrapper.scrollTop = 40;
mounted.instance().lastScrollTop = 40;
mounted.setProps({
...props,
data: [{ val: 'someNewData' }],
});
// mounted.instance().componentDidUpdate({});
expect(gridWrapper.scrollTop).toEqual(0);
expect(mounted.instance().lastScrollTop).toEqual(0);
});
When I uncomment out // mounted.instance().componentDidUpdate({});, it works as expected, however I'm expecting the componentDidUpdate method to be called with the props changing. I checked, the props are updating as they should so I was wondering if this is a known issue or if it's something I'm doing wrong?
@Jmac1523 try adding mounted.update().
@ljharb I tried, unfortunately to no avail. Is the test less-effective or even ineffective with mounted.instance().componentDidUpdate({}); ?
@Jmac1523 can you file a new issue?
sure, thanks for getting back to me!
@Jmac1523 that instance call will only work on state based components. I suppose lifecycle methods only exist in those but then there is the challenge of testing the useEffect method (on update, render, etc.). IMHO there needs to be some support for this from the Enzyme team. _shallow_ doesn't buy you much as Enzyme _shallow_ isn't going to be tied into your lifecycle methods.
Err...I know, I never said I was using Hooks
@Jmac1523 I realize that but with so many components being developed with hooks and moving away from state based it's worth noting. If you find any leads on this let me know. I'm doing some research on a state based approach now. This is something that needs attention from the Enzyme team.
@Jmac1523 and everyone else. This solution seems to work nicely for testing state-based component lifecycle methods:
it('should call componentDidUpdate', () => {
let node = document.createElement('div');
let instance = ReactDOM.render(table, node);
const spy = jest.spyOn(Table.prototype, 'componentDidUpdate');
ReactDOM.render(modifiedTable, node);
expect(spy).toHaveBeenCalled();
})
This is hitting that component during runtime:

Ultimately what I'm doing here is setting the table (JSX) on the DOM then just loading in a table with modified properties. Here's the table in Jest:
const justTheTable = (
<Table
data={fossilsMainDirectory}
defaultSortType="string"
defaultSortKey="class"
defaultSortAsc={true}
columnConfiguration={columns.desktop}
tabletOverrideColumnConfiguration={columns.tablet}
mobileOverrideColumnConfiguration={columns.mobile}
>
{({ filter }) => {
return (
<>
<h1>Hello Spec</h1>
</>
);
}}
</Table>
);
const table = (
<ThemeProvider theme={theme}>
{justTheTable}
</ThemeProvider>
);
Most helpful comment
@bricejlin
318 is hidden in the
lifecycleExperimentalflag.You can do with the flag for enabling all lifecycle methods in
shallow.