为什么Jest在componentDidMount完成执行之前(使用React Test Renderer)运行断言?

时间:2019-05-15 23:01:29

标签: reactjs react-native jestjs

如果您想直接复制,

Here是此问题的存储库。

我有一个新创建的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();
  });
});

这是运行测试的结果: enter image description here

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

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

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

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

1 个答案:

答案 0 :(得分:1)

async / await只是针对Promise和Generator的语法糖。

调用await时,实际上是将其余功能放在等待的then上的Promise中。

这意味着Promise解决后,其余功能将添加到PromiseJobs队列中。

PromiseJobs队列中的

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

在这种情况下,此行运行:

await actions.funcOne();

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

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

要使测试通过,只需给在PromiseJobs中排队的回调提供运行的机会。

如您所见,可以使用以下行完成此操作:

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

...但更容易的是await解决的Promise

await Promise.resolve();

这会将其余的 test 排队在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!
});