enzyme: mount/shallow does not rerender when props change or apply new props on update
With Enzyme v2 you could mount/shallow render a component with some props and if those props changed then the component would be updated appropriately. With v3 even when you explicitly call .update()
it fails to apply the updated version of the props.
Two example tests are shown below; the first would work in v2, but v3 requires explicitly calling .setProps
to force it to update. Is this expected behaviour in v3? I assume it’s a consequence of the new adapters, but I couldn’t see anywhere in the migration guide that it was mentioned.
import React from 'react';
import { shallow } from 'enzyme';
import { describe, it } from 'mocha';
import { expect } from 'chai';
import enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
enzyme.configure({ adapter: new Adapter() });
class Thing extends React.Component {
render() {
return (
<div>
<button onClick={this.props.onClick}>Click me</button>
<ul>
{this.props.things.map(id => <li key={id}>{id}</li>)}
</ul>
</div>
);
}
}
describe('<Thing>', () => {
it('updates the things FAIL', () => {
const things = [];
const onClick = () => things.push(things.length);
const wrapper = shallow(<Thing things={things} onClick={onClick} />);
expect(wrapper.find('li')).to.have.length(0);
wrapper.find('button').simulate('click');
// Does not reapply props?
wrapper.update();
expect(things).to.have.length(1); // things has been modified correctly
expect(wrapper.find('li')).to.have.length(1); // but the change is not reflected here
});
it('updates the things OK', () => {
const things = [];
const onClick = () => things.push(things.length);
const wrapper = shallow(<Thing things={things} onClick={onClick} />);
expect(wrapper.find('li')).to.have.length(0);
wrapper.find('button').simulate('click');
// Forcing new things to be applied does work
wrapper.setProps({ things });
expect(things).to.have.length(1);
expect(wrapper.find('li')).to.have.length(1); // this time the change is correctly reflected
});
});
Package versions
react@16.0.0 react-dom@16.0.0 enzyme@3.1.0 enzyme-adapter-react-16@1.0.1
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 73
- Comments: 35 (8 by maintainers)
I’ve discovered the solution for me, hope to help others but they may have another issue:
will not work as I was trying to check a found child of the wrapper which is immutable. Per this thread: https://github.com/airbnb/enzyme/issues/1221#issuecomment-334974967 You have to find the child again to get the new instance with the new props. This works instead:
@ljharb for some reason I didn’t understand what you meant in this thread earlier, but the linked comment is more clear about immutability/re-finding, thanks!
This was an intentional design choice in v3. I’m going to close this; please file new issues for actionable concerns.
Workaround for now is to use
wrapper.update()
for me http://airbnb.io/enzyme/docs/api/ShallowWrapper/update.html.render()
doesn’t seem to be called when calling.update()
. Mine worked only when I called both of these in order:@adrienDog as noted in the issue description and reproduction code,
.update()
does not work 😕@adrienDog it’s recommended to use immutable data with react. In your example you’re pushing data into the array, so it’s reference continues the same and for the component receiving
things
it hasn’t changed. Try using:So this way
things
reference will change, thus changing the props, thus triggering the update.I have the same issue, but this seems to also occur when updating state internally in the Component.
@DorianWScout24 because that ensures it gets the issue template filled out, and it avoids pinging all the people on this thread with a potentially different problem, and it helps the maintainers (hi) properly triage, categorize, and pay attention to the fact that there’s a problem. This issue was asking about something that’s not a bug, but rather a design choice for v3, so in this thread there’s nothing to fix.
@marchaos @adrienDog calling
wrapper.update()
didn’t work for me but callingwrapper.instance().forceUpdate()
worked.I’m seeing this, or something very similar too. Using enzyme 3.1.0, react 16.2.0, enzyme-adapter-react-16 1.0.4, and
setState
on a component, or simulating a click on a button which causes an internalsetState
, does not send new props to child components.@shaundavin13 please file a new issue if you’re having trouble.
No matter which order you set state, change state value, update, or force update, child components never get the props right
Its a shallow wrapper, by the way
@ericschultz generally, you need to always re-find from the root to get updates, and you may need to use
wrapper.update()
before doing that. The migration guide talks about that here: https://github.com/airbnb/enzyme/blob/master/docs/guides/migration-from-2-to-3.md#element-referential-identity-is-no-longer-preserved@ljharb As this is an intentional design choice, is there a migration path or explanation on how to do what folks on this thread want? I’m happy to change my tests but based on the thread, I still have no idea what to change it to.
What seemed to work for me was adding a
unmount
immediatly after amount
onbeforeEach
Same here.
forceUpdate not working for
mount
, onlyshallow
.This is surprising to me…if one of my tests calls method
_x
on a shallow-rendered component, and_x
itself callssetState
, I would expect that that test can assert that spies called bycomponentDidUpdate
have been called (unless I have disableLifecycleMethods on).Instead, it seems that’s only true if I call
wrapper.setState
directly from my test.Refind still does not work.
Nope, but it looks like while ~6~ 21 people are unhappy about my suggestion about how to get their issue fixed, none of them have been motivated enough to do anything about it.
Why should we open a new issue if this is still not solved?
I had to use a timeout to get the Provider to update:
I just solved my problem. Diving in the listComponent prevented it from rerendering it. The code worked when I removed the code to dive to tableComponent.
No need of the listComponent.update() or other calls. By the way, the shallow rendering and dive code was written in the beforeEach segment.
@adrienDog probably you’ll need to pass the new props anyway, that’s the correct way. In the first example the wrapper is updating the component with the old props.