【问题标题】:Wait for a React Component to finish updating等待 React 组件完成更新
【发布时间】:2017-05-08 10:10:06
【问题描述】:

在我的测试中,我想阻止我的主线程,直到我的一个组件完成其生命周期方法,通过componentDidUpdate(),在我触发一个导致它向自身添加子组件的事件之后。我该怎么做?

类似这样的:

describe('my <Component />', () => {
  it('should do what I want when its button is clicked twice', () => {
    const wrapper = mount(<Component />);
    const button = wrapper.find('button');

    // This next line has some side effects that cause <Component /> to add
    // some children components to itself...
    button.simulate('click', mockEvent);

    // ... so I want to wait for those children to completely go through
    // their lifecycle methods ...
    wrapper.instance().askReactToBlockUntilTheComponentIsFinishedUpdating();

    // ... so that I can be sure React is in the state I want it to be in
    // when I further manipulate the <Component />
    button.simulate('click', mockEvent);

    expect(whatIWant()).to.be.true;
  });
});

(我想这样做是因为,现在,我收到了这个警告:

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.

我相信我得到了它,因为我的测试导致我的组件更改其内部状态的速度比 React 的内部多线程魔法可以跟上的速度更快,所以当我第二次运行 button.simulate('click') 时,React 已经实例化新的子组件,但尚未完成安装它们。我认为等待 React 完成更新我的组件及其子组件是解决该问题的最佳方法。)

【问题讨论】:

  • 点击处理程序是做什么的?所有状态更改都应该是同步的,除非你特别有一些计时器/异步的东西在进行。
  • DOM 更新也是同步的,除非你正在做一些奇怪的事情或使用 React 的异国风味。如果您的setState 警告是因为您的组件本身在挂载前执行了setState,我不会感到惊讶。
  • @Jacob 我正在使用第三方库,React Widgets。我的 &lt;Component /&gt; 有一个 React Widgets DateTimePicker 作为它的孩子之一,警告似乎是从 DateTimePicker 内部发出的。所以不幸的是,我完全确定我的事件会导致什么变化,而且我很容易无法尝试查看组件以确保它没有行为不端。
  • 当我在浏览器中运行我的应用程序时,我没有收到警告,即使我执行了我在测试中模拟的相同操作,所以我相信这个问题是特定于我的测试环境如何交互的使用我的组件,而不是我的组件中的错误(尽管我很可能是错的)。
  • 最后,你确定 React 应该同步更新组件吗?在a conversation I had in chat,有人suggested that's not the case。当我在 settimeout 块中包围我的部分代码以在我模拟的事件之间提供一瞬间的时间时,警告消失了,这真的让我觉得 React 正在做一些我希望我的测试等待的异步操作为。

标签: javascript reactjs mocha.js chai enzyme


【解决方案1】:

尝试将您的 expect() 块包装在 setImmediate() 块中:

describe('my <Component />', () => {
  it('should do what I want when its button is clicked twice', (done) => {
    const wrapper = mount(<Component />);
    const button = wrapper.find('button');

    button.simulate('click', mockEvent);
    button.simulate('click', mockEvent);

    setImmediate(() => {
      expect(whatIWant()).to.be.true;
      done();
    });
  });
});

这是发生了什么:为了处理异步性,Node 和大多数浏览器在幕后都有一个事件队列。每当需要异步运行诸如 Promise 或 IO 事件之类的东西时,JS 环境会将其添加到队列的末尾。然后,每当一段同步代码完成运行时,环境就会检查队列,选择队列前面的任何内容并运行它。

setImmediate() 在队列后面添加一个函数。一旦当前队列中的所有内容完成运行,传递给setImmediate() 的函数中的任何内容都将运行。因此,无论 React 异步执行什么操作,将您的 expect()s 包装在 setImmediate() 中都会导致您的测试等待 React 完成它在后台执行的任何异步工作。

这是一个很好的问题,提供有关setImmediate() 的更多信息:setImmediate vs. nextTick

这是 Node 中 setImmediate() 的文档:https://nodejs.org/api/timers.html#timers_setimmediate_callback_args

【讨论】:

  • 我在一些测试中尝试过这个setImmediate() 技巧,但在我的问题中描述的具体情况下没有。所以有龙,这个解决方案可能行不通。
  • 当断言 (expect) 在 setImmediate() 中失败时,测试会以错误终止,而不是将其标记为 Fail 并继续。有什么解决办法吗?
  • @VLeong 您是否通过在it 块中使用done 函数将测试编写为异步测试?见mochajs.org/#asynchronous-code
猜你喜欢
  • 2016-02-17
  • 2021-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多