enzyme: setState does not set state

Describe the bug A clear and concise description of what the bug is.

I’ve setup versions enzyme 3.5.0 and enzyme-adapter-react-16 1.3.0. I’ve created an enzyme test in this commit https://github.com/polkadot-js/apps/pull/265/commits/0d048c094b91762ac6a7812f5d4c5899b71b5af5 that mounts the component that passes. If I run a test with expect(wrapper.get(0)).toBe(1); it will show me that the component includes the correct props along with provided fixtures <Signer queue={[{"id": "test", "rpc": {"isSigned": true}, "status": "test"}]} t={[Function mockT]} />. So far the test I’ve written that checks expect(wrapper).toHaveLength(1); is passing successfully. However, I want to run a test to check that the <Modal className='ui--signer-Signer' ... (see https://github.com/polkadot-js/apps/blob/master/packages/ui-signer/src/Modal.tsx#L89) renders correctly, but it only renders when this.state.currentItem and this.state.currentItem.rpc.isSigned (see https://github.com/polkadot-js/apps/blob/master/packages/ui-signer/src/Modal.tsx#L83) are defined and true. So in the commit I created fixtures for the the currentItem state value, and wrote the following test to set the state with setState, update the component, and then check that the state has changed. But it doesn’t appear to work, because the test results report that currentItem is still undefined instead of being the value I set to the fixtureCurrentItemState variable.

  it('sets the state of the component', () => {
    wrapper.setState({ currentItem: fixtureCurrentItemState });
    wrapper = wrapper.update(); // immutable usage
    expect(wrapper.state('currentItem')).toEqual(fixtureCurrentItemState);
    console.log(wrapper.debug());
  });

Note that I tried debugging with console.log(wrapper.debug()); and console.log(wrapper.html());, which I’ve used in the past without any issues, but neither of them output anything, so as an alternative I was able to check the state by running expect(wrapper.state()).toEqual(1);, which returned {"currentItem": undefined, "password": "", "unlockError": null}

To Reproduce Steps to reproduce the behavior:

  1. Go to https://github.com/polkadot-js/apps
  2. Clone Pull Request https://github.com/polkadot-js/apps/pull/265
  3. Install dependencies and run the tests with yarn; yarn run build; yarn run test;
  4. Remove .skip from the above mentioned tests
  5. See the failing test

Expected behavior I expected setState to set the state

Screenshots Code used screenshot: screen shot 2018-08-27 at 9 36 33 am

Failing test screenshot: screen shot 2018-08-27 at 9 38 47 am

Desktop (please complete the following information):

  • OS: macOS
  • Browser: Chrome
  • Version 68.0.3440.106

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 1
  • Comments: 26 (8 by maintainers)

Commits related to this issue

Most upvoted comments

Yeah I seem to be running into this same issue. I have an instance method that sets state, so I run the instance method and ensure the the correct state gets set:

wrapper.instance().reviewApp({ uid: 'some_id' });
expect(History.push).toHaveBeenCalledWith('/mobile/apps/detail/some_id');
expect(wrapper.state('adcOpen')).toBeTruthy(); //is false

Enzyme 3.5.0 enzyme-adapter-react-16 1.3.1

I changed it to manually call setState on the wrapper, but the state still isn’t set.

wrapper.setState({adcOpen: true});
expect(History.push).toHaveBeenCalledWith('/mobile/apps/detail/some_id');
expect(wrapper.state('adcOpen')).toBeTruthy(); //Still false

This still doesn’t set the state correctly. I even tried it with the callback on setState and still no-go.

wrapper.setState({adcOpen: true}, () => {
  expect(History.push).toHaveBeenCalledWith('/mobile/apps/detail/some_id');
  expect(wrapper.state('adcOpen')).toBeTruthy(); //Still false
});

EDIT: Also, I’m on React 16.4.2

fwiw, wrapper = wrapper.update(); isn’t required; “immutable” just means you have to re-find from the root.

setState is async; what happens if you do:

wrapper.setState({ currentItem: fixtureCurrentItemState }, () => {
  wrapper.update();
  expect(wrapper.state('currentItem')).toEqual(fixtureCurrentItemState);
  console.log(wrapper.debug());
});

@ltfschoen again, await is useless on setState and setProps, since neither returns a promise.

@ljharb I tried again and it doesn’t work with the following versions. I tried using Callbacks, Promises, and Async Await, but cannot change the state using the below code.

  • “enzyme”: “^3.5.0”, “enzyme-adapter-react-16”: “^1.3.0”,
  • “enzyme”: “^3.4.4”, “enzyme-adapter-react-16”: “^1.2.0”,
  • “enzyme”: “3.3.0”, “enzyme-adapter-react-16”: “1.1.1”,
it('set the state of the component using callbacks and anonymous function', (done) => {
  wrapper.setState({ currentItem: fixtureCurrentItemState }, () => {
    expect(wrapper.update().state('currentItem')).toEqual(fixtureCurrentItemState);
    expect(wrapper.update().find('.ui--signer-Signer')).toHaveLength(1);
    done();
  });
});

it('set the state of the component using callbacks and named functions', (done) => {
  const checkExpectation = (done) => {
    expect(wrapper.update().state('currentItem')).toEqual(fixtureCurrentItemState);
    done();
  };

  const doAsyncAction = (callback, done) => {
    wrapper.setState({ currentItem: fixtureCurrentItemState }, () => {
      callback(done);
    });
  };

  doAsyncAction(checkExpectation, done);
});

it('set the state of the component using promises', () => {
  Promise.resolve(wrapper.setState({ currentItem: fixtureCurrentItemState }))
    .then(_ => {
      expect(wrapper.update().state('currentItem')).toEqual(fixtureCurrentItemState);
      expect(wrapper.update().find('.ui--signer-Signer')).toHaveLength(1);
    })
    .catch((error) => console.log('error', error));
});

it('set the state of the component using async await', async () => {
  try {
    await wrapper.setState({ currentItem: fixtureCurrentItemState });

    expect(wrapper.update().state('currentItem')).toEqual(fixtureCurrentItemState);
    expect(wrapper.update().find('.ui--signer-Signer')).toHaveLength(1);
  } catch (error) {
    console.error(error);
  }
});

you can setState like this

  it('sets the state of the component', () => {
    const componentInstance = wrapper
      .childAt(0)
      .instance();
    componentInstance.setState({ currentItem: fixtureCurrentItemState });
    wrapper = wrapper.update(); 
    expect(wrapper.state('currentItem')).toEqual(fixtureCurrentItemState);
  });

will pass

see my gist

https://gist.github.com/elvisgiv/e9056bde1cfbf5bcfecdfb32c10577ea

@ltfschoen don’t use try/catch on your code there, return a new Promise instead, that resolves on the callback:

return new Promise((resolve) => {
  wrapper.setState({ currentItem: fixtureCurrentItemState }, () => {
      wrapper.setProps({
        queue: fixtureQueueProp
      }, resolve);
  });
}).then(() => {
  wrapper.update();
  expect(wrapper.state('currentItem')).toEqual(expectedNextCurrentItemState);
  expect(wrapper.find('.ui--signer-Signer')).toHaveLength(1);
});

@comfroels thanks!

it('checks state setting', async () => {
    const wrapper = shallow(<Link title="Click"/>);
    try {
        await wrapper.setState({value: 'I need to do something...'});
        const textInput = wrapper.find('TextInput').first();
        expect(textInput.prop('value')).toStrictEqual('I need to do something...');
        console.log(wrapper.debug());
    } catch (error) {
        console.error(error);
    }
});

This works for me

Yes, I do believe I have a bad pattern that I’m using, as upgrading to React 16.5 causes me a very similar issue. You can close this, thanks for all the help ladies/gents!

@jgzuke Yes, I’ve established why it wasn’t setting state. I’ve changed my code to use callbacks with the following, but I’d like to refactor it to use promises instead.

  it('testing', (done) => {
    try {
      wrapper.setState({ currentItem: fixtureCurrentItemState }, () => {
        wrapper.setProps({
          queue: fixtureQueueProp
        }, () => {
          wrapper.update();
          expect(wrapper.state('currentItem')).toEqual(expectedNextCurrentItemState);
          expect(wrapper.find('.ui--signer-Signer')).toHaveLength(1);

          console.log(wrapper.debug());
          done();
        });
      });
    } catch (error) {
      console.error(error);
    }
  });

I’m now trying to use mount instead of shallow, so I’ve mocked i18n with the following to overcome error TypeError: Cannot read property 'options' of undefined

jest.mock('react-i18next', () => ({
  // this mock makes sure any components using the translate HoC receive the t function as a prop
  translate: () => Component => props => <Component t={k => k} {...props} />
}));

But now it’s giving error TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined

Thanks for that link, it’s awesome!

Sorry, haven’t responded to this yet, the component in question for me, doesn’t use getDerivedStateFromProps at all. I’ll be probably playing around more with this today. So I’ll check enzyme 3.4 and see if I have the same issue. I was going to try to get us up to React 16.5 today

React calls getDerivedStateFromProps when a component receives either new props or state. In the original example, Signer has a getDerivedStateFromProps that assigns state.currentItem from a prop value.