Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wait until React component completely finished updating in Jest and/or Enzyme?

In my create-react-app, I am trying to test a component that does multiple setStates when mounted.

class MyComponent extends React.Component {

  state = {
    a: undefined,
    b: undefined,
    c: undefined,
  };

  fetchA() {
    // returns a promise
  }

  fetchB() {
    // returns a promise
  }

  fetchC() {
    // returns a promise
  }

  async componentDidMount() {
    const a = await fetchA();
    this.setState({ a });
  }

  async componentDidUpdate(prevProps, prevState) {
    if (prevState.a !== this.state.a) {
      const [b, c] = await Promise.all([
        this.fetchB(a);
        this.fetchC(a);
      ]);
      this.setState({ b, c });
    }
  }

  ...

}

In my test, I do something like this, trying to let the setState in componentDidUpdate to finish before making assertions.

import { mount } from 'enzyme';

describe('MyComponent', () => {

  const fakeA = Promise.resolve('a');
  const fakeB = Promise.resolve('b');
  const fakeC = Promise.resolve('c');

  MyComponent.prototype.fetchA = jest.fn(() => fakeA);
  MyComponent.prototype.fetchB = jest.fn(() => fakeB);
  MyComponent.prototype.fetchC = jest.fn(() => fakeC);

  it('sets needed state', async () => {
    const wrapper = mount(<MyComponent />);
    await Promise.all([ fakeA, fakeB, fakeC ]);
    expect(wrapper.state()).toEqual({
      a: 'a',
      b: 'b',
      c: 'c',
    });
  });

});

Here's the interesting part: My test above will fail because the last setState call (in componentDidUpdate) has not finished when the assertion is made. At that time, state.a is set, but state.b and state.c is not set yet.

The only way I could make it work is by wedging await Promise.resolve(null) right before the assertion to give the last setState that extra tick/cycle to complete. This looks too hacky.

Another thing I've tried is wrapping the assertion in setImmediate(), which works fine as long as the assertion passes. If it fails, it will terminate the whole test because of uncaught error.

Has anyone overcome this problem?

like image 330
Vicky Leong Avatar asked Aug 22 '17 03:08

Vicky Leong


1 Answers

This is how I solved it. Hope that helps someone.

import { mount } from 'enzyme';

describe('MyComponent', () => {

  const fakeA = Promise.resolve('a');
  const fakeB = Promise.resolve('b');
  const fakeC = Promise.resolve('c');

  MyComponent.prototype.fetchA = jest.fn(() => fakeA);
  MyComponent.prototype.fetchB = jest.fn(() => fakeB);
  MyComponent.prototype.fetchC = jest.fn(() => fakeC);

  it('sets needed state', async (done) => {
    const wrapper = mount(<MyComponent />);
    await Promise.all([ fakeA, fakeB, fakeC ]);

    setImmediate(() => {
      // Without the try catch, failed expect will cause the
      // whole test to crash out.
      try {
        expect(wrapper.state()).toEqual({
          a: 'a',
          b: 'b',
          c: 'c',
        });
      } catch(error) {
         done.fail(error);
      }
      done();

  });

});
like image 122
Vicky Leong Avatar answered Sep 30 '22 19:09

Vicky Leong