enzyme: ReactWrapper.update() is not forcing a re-render

Current behavior

min-repro here: https://github.com/nicholasrice/enzyme-react-wrapper-update-repro

Using mount to mount a component, calling the update method of the returned ReactWrapper instance does not seem to be forcing a re-render. With slight changes, I implemented the example from https://airbnb.io/enzyme/docs/api/ReactWrapper/update.html and am expierencing a test failure.

On a slight aside, I think the assertions being made in the above documentation should be 1 for the first call and 2 for the second call, instead of 0 for the first call and 1 for the second.

const React = require('react');
const Enzyme = require('enzyme');
const Adapter = require('enzyme-adapter-react-16');

Enzyme.configure({ adapter: new Adapter() });

test("ReactWrapper.update should re-render a react component", () => {
    const spy = jest.fn();

    class ImpureRender extends React.Component {
        constructor(props) {
            super(props);

            this.count = 0;
        }

        render() {
            this.count += 1;
            spy(this.count);

            return React.createElement("div", {}, this.count);

        }

    }

    const wrapper = Enzyme.mount(React.createElement(ImpureRender));

    expect(spy).toHaveBeenCalledTimes(1)

    // Update the component - should force re-render per https://github.com/airbnb/enzyme/blob/master/docs/api/ReactWrapper/update.md
    wrapper.update();

    expect(spy).toHaveBeenCalledTimes(2)
});

Expected behavior

I would expect that calling the update method of a ReactWrapper would call the render method of the mounted component.

Your environment

API

  • shallow
  • mount
  • render

Version

library version
enzyme 3.9.0
react 16.8.4
react-dom 16.8.4
react-test-renderer 16.8.4
adapter (below) 1.10.0

Adapter

  • enzyme-adapter-react-16

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 17
  • Comments: 32 (9 by maintainers)

Most upvoted comments

forceUpdate won’t work in a functional component though, as there is no instance

I’m having the same issue.

Doing wrapper.instance().forceUpdate() works for me but looking at the documentation I would expect wrapper.update() to do the same. I don’t know how “safe” using forceUpdate is but for now it’s fixed the problem for me.

Actually, since setProps expect next props to merge, component.setProps() should be enough to trigger a re-render

This code works for functional component:

    let component = mount(<test.component />);
    component.setProps({ foo: 42 }); // re-render

Actually, my component even don’t need to have a prop foo.

I’m having the same issue.

Doing wrapper.instance().forceUpdate() works for me but looking at the documentation I would expect wrapper.update() to do the same. I don’t know how “safe” using forceUpdate is but for now it’s fixed the problem for me.

TypeError: wrapper.instance(...).forceUpdate is not a function … I got this error

Event wrapped.instance().forceUpdate() doesn’t re-render 😦

let wrapped;
beforeEach(() => {
    wrapped = mount(
      <Provider store={store}>
        <Test />
      </Provider>
   );
});

it("disables submit button if it's already uploading a file", () => {
    const uploadButton = wrapped.find("#uploadButton");
    expect(uploadButton.prop("disabled")).toEqual(false);

    wrapped.instance().setState({ uploading: true }, () => {
        wrapped.instance().forceUpdate();
        
        expect(uploadButton.prop("disabled")).toEqual(false);
        // This is supposed to fail, but it passes
    });
});

setProps fixed the problem for me but doesnt seem like a real solution

Following @PFight idea, this generic approach worked for me:

const component = mount(<YourComponent {...anyProps} />);
component.setProps(); // Forces react component tree to re-render.
component.update(); // Syncs the enzyme component tree snapshot with the react component tree.

@shaun-weddell - You’re absolutely right. For React FC - the instance() method will return null. So, obviously we can’t call forceUpdate()

Event wrapped.instance().forceUpdate() doesn’t re-render 😦

let wrapped;
beforeEach(() => {
    wrapped = mount(
      <Provider store={store}>
        <Test />
      </Provider>
   );
});

it("disables submit button if it's already uploading a file", () => {
    const uploadButton = wrapped.find("#uploadButton");
    expect(uploadButton.prop("disabled")).toEqual(false);

    wrapped.instance().setState({ uploading: true }, () => {
        wrapped.instance().forceUpdate();
        
        expect(uploadButton.prop("disabled")).toEqual(false);
        // This is supposed to fail, but it passes
    });
});
```This works

Hmm, that does make sense. It does stand to reason that a call to update forces a rerender.

yes, i guess my code snippet above proves your point. If i was mocking before i created the wrapper in each test I wouldnt have the problem I do. Thanks!

The crazy part is the setProps actually does fix it though and let me re-mock the value. Maybe just a happy side-effect

The difference is that each test that has a tiny modification can change it without shenanigans like “oops, let me update the wrapper and rerender it”, and that failure messages are clearer.

@mmassaki you’re right! just fixed the suggestion! thanks!

I’m having the same issue.

Doing wrapper.instance().forceUpdate() works for me but looking at the documentation I would expect wrapper.update() to do the same. I don’t know how “safe” using forceUpdate is but for now it’s fixed the problem for me.

Im not 100% sure but i think that:

Enzyme’s update() method checks if props/state changed and based on that decides to update component or not.

forceUpdate() in this case is actually React’s method. Notice that you don’t call it at wrapper but at wrapper.instance(). It causes a re-render no matter what https://reactjs.org/docs/react-component.html#forceupdate.

I’d say that this is acceptable solution.

They call it when state and/or props change, usually.

I’m just saying we’ll have to be careful about it, and that it might be better to refactor so that the current behavior is preserved for all internal update attempts.

Since this relies on an impure render (which is obviously a very bad idea in React), can you elaborate on the actual use case where this is popping up? If props and state and context haven’t changed, a rerender shouldn’t need to call render, even via enzyme’s update.