web-dev-qa-db-ja.com

ReactコンポーネントがJestやEnzymeで完全に更新を完了するまで待つ方法は?

Create-react-appで、マウント時に複数のsetStateを実行するコンポーネントをテストしようとしています。

_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 });
    }
  }

  ...

}
_

私のテストでは、アサーションを作成する前に、setStatecomponentDidUpdateを終了させようとしてこのようなことを行います。

_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',
    });
  });

});
_

興味深い部分は次のとおりです。アサーションが行われたときに最後のsetState呼び出し(componentDidUpdate内)が終了していないため、上記のテストは失敗します。その時、_state.a_は設定されていますが、_state.b_と_state.c_はまだ設定されていません。

私がそれを機能させる唯一の方法は、アサーションの直前にawait Promise.resolve(null)をくさびで留めて、最後のsetStateにその余分なティック/サイクルを完了させることです。これはあまりにもハッキーに見えます。

私が試したもう1つのことは、アサーションをsetImmediate()でラップすることです。これは、アサーションが通過する限り正常に機能します。失敗した場合、キャッチされなかったエラーのためにテスト全体が終了します。

誰かがこの問題を克服しましたか?

11
Vicky Leong

これが私がそれを解決した方法です。それが誰かを助けることを願っています。

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();

  });

});
4
vleong