【问题标题】:How to wait until React component completely finished updating in Jest and/or Enzyme?如何等到 React 组件在 Jest 和/或 Enzyme 中完全完成更新?
【发布时间】:2017-08-22 03:00:11
【问题描述】:

在我的 create-react-app 中,我正在尝试测试一个组件,该组件在安装时会执行多个 setStates。

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

  ...

}

在我的测试中,我做了这样的事情,试图让componentDidUpdate中的setState在做出断言之前完成。

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.bstate.c

我可以使它工作的唯一方法是在断言之前将await Promise.resolve(null) 楔入,以使最后一个setState 完成额外的滴答声/周期。这看起来太老套了。

我尝试过的另一件事是将断言包装在setImmediate() 中,只要断言通过,它就可以正常工作。如果失败,它将因为未捕获的错误而终止整个测试。

有人解决过这个问题吗?

【问题讨论】:

  • 如果将Promise.all 语句替换为两个语句,例如const b = await this.fetchB(a) ...,它会起作用吗?或者模拟Promise.all。我的猜测是,这个问题是你在那里创建的承诺,开玩笑不知道。
  • 你是指组件中的Promise.all还是测试中的Promise.all?如果它在测试中,我很确定我确认 jest 等待所有三个完成。但是,只要 3 个 Promise 完成,测试就会恢复,而无需等待 componentDidUpdate 中的最后一个 setState 完成。不管怎样,我会试着按照你说的去做。
  • 这个问题你解决了吗?
  • 是的。看看我在下面发布的答案。

标签: reactjs unit-testing asynchronous jestjs enzyme


【解决方案1】:

我就是这样解决的。希望对某人有所帮助。

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

  });

});

【讨论】:

  • 最好的方法是测试组件的输出,而不是它的内部状态。对于一个好的测试,它应该能够让你在不影响输出和测试的情况下重构内部。
  • @OmriLuzon 我同意你的观点,但通常在内部状态更新之前输出不会改变。在测试输出之前如何知道状态已更新?
  • 解释为什么这会更有帮助。我的意思是为什么setImmediate() 使它起作用。毕竟它立即执行闭包,所以它和问题中的代码是一样的。 setImmediate 有什么不同?
猜你喜欢
  • 2017-05-08
  • 1970-01-01
  • 2020-11-02
  • 1970-01-01
  • 2016-09-18
  • 1970-01-01
  • 1970-01-01
  • 2018-05-02
  • 2017-04-13
相关资源
最近更新 更多