【问题标题】:Why does Jest run assertions before componentDidMount finishes executing (using React Test Renderer)?为什么 Jest 在 componentDidMount 完成执行之前运行断言(使用 React Test Renderer)?
【发布时间】:2019-05-15 23:01:28
【问题描述】:

Here是这个问题的repo,如果你想直接复现的话。

我有一个新创建的 react-native 项目(我认为这个问题是 React 还是 React-Native 并不重要)。我只有一个组件App.js

import React, { Component } from 'react';
import { View } from 'react-native';
import actions from './actions';

export class App extends Component {
  async componentDidMount() {
    console.log('In CDM');
    await actions.funcOne();
    await actions.funcTwo();
    console.log('Finished CDM');
  }

  render() {
    return <View />;
  }
}

这是该组件从actions.js 导入的两个函数:

const funcOne = async () => {
  console.log('One');
};

const funcTwo = async () => {
  console.log('Two');
};

export default { asyncOne: funcOne, asyncTwo: funcTwo };

这是我写的一个测试:

import React from 'react';
import { App } from '../App';
import renderer from 'react-test-renderer';

import actions from '../actions';

const spyOne = jest.spyOn(actions, 'funcOne');
const spyTwo = jest.spyOn(actions, 'funcTwo');

describe('App ', () => {
  test('does async stuff in expected order', async () => {
    console.log('Starting test');
    const tree = await renderer.create(<App />);
    console.log('About to expect');
    expect(spyOne).toHaveBeenCalled();
    console.log('Expect one to have been called');
    expect(spyTwo).toHaveBeenCalled();
    console.log('Expect two to have been called');
    expect(tree).toMatchSnapshot();
  });
});

这是运行测试的结果:

可以看出,第二个expect 断言在funcTwo 函数在componentDidMount 中执行之前被调用。

我实际上想要完成的是我有一个更复杂的组件,它在componentDidMount 中执行一个异步函数(例如进行 API 调用)。我希望我的测试创建组件树,并断言组件确实调用了相关函数。

我实际上找到了一个“解决方案”(它使我的测试通过并且console.logs以正确的顺序出现,但我不明白它为什么起作用。解决方案是在测试中添加await (() =&gt; new Promise(setImmediate))();行在 await renderer.create 行之后的文件。

**所以,我不只想要一个解决方案(尽管如果您有理想的解决方案,请提供)。我想知道这里发生了什么,为什么原始代码不能按预期工作? **

【问题讨论】:

    标签: reactjs react-native jestjs


    【解决方案1】:

    async / await 只是 Promise 和生成器的语法糖。

    当您调用 await 时,您实际上将函数的其余部分排队到附加到您正在等待的 Promisethen 中。

    这意味着当Promise解析时,函数的其余部分被添加到PromiseJobs队列中。

    Promise PromiseJobs 队列中的回调在当前消息完成后运行...这意味着任何同步代码都将在回调有机会运行之前完成。

    在这种情况下,这条线运行:

    await actions.funcOne();
    

    ...同步调用funcOne。它会立即解析,因此componentDidMount 的其余部分被放置在 PromiseJobs 队列中,然后执行返回到测试。 (请注意,在renderer.create 上调用await 不会等待componentDidMount 返回的Promise

    测试的其余部分是同步的,因此它运行第一个通过的 expect,然后运行第二个失败的 expect,因为 componentDidMount 的其余部分仍在 PromiseJobs 队列中等待。

    要让测试通过,你只需要给在 PromiseJobs 中排队的回调一个运行的机会。

    正如你所发现的,这可以通过这一行来完成:

    await (() => new Promise(setImmediate))();
    

    ...但更简单的方法是 await 一个已解决的 Promise:

    await Promise.resolve();
    

    这会将其余的 测试 排在回调后面的 PromiseJobs 队列后面,该回调将调用 actions.funcTwo 并且测试将通过。

    这里有一个稍微简化的例子来演示:

    import * as React from 'react';
    import renderer from 'react-test-renderer';
    
    const f1 = jest.fn();
    const f2 = jest.fn();
    
    class App extends React.Component {
      async componentDidMount() {
        await f1();
        await f2();
      }
      render() { return null; }
    }
    
    test('does async stuff in expected order', async () => {
      const tree = renderer.create(<App />);
      expect(f1).toHaveBeenCalled();  // Success!
      await Promise.resolve();  // <= let any callbacks in PromiseJobs run
      expect(f2).toHaveBeenCalled();  // Success!
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-12
      • 2020-11-14
      • 2021-12-27
      • 2022-08-05
      • 1970-01-01
      • 1970-01-01
      • 2017-11-28
      相关资源
      最近更新 更多