Почему Jest запускает утверждения до того, как componentDidMount завершит выполнение (используя React Test Renderer)? - PullRequest
2 голосов
/ 16 мая 2019

Здесь - это репозиторий для этого вопроса, если вы хотите напрямую воспроизвести.

У меня есть недавно созданный реактивно-нативный проект (думаю, что для этого вопроса не важно, является ли он 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.

Что я на самом деле пытаюсь сделать, так это то, что у меня есть гораздо более сложный компонент, который выполняет асинхронную функцию (которая, например, выполняет вызовы API) в componentDidMount. Я хочу, чтобы мой тест создал дерево компонентов, и утверждаю, что компонент действительно выполнял вызовы соответствующих функций.

Я на самом деле нашел «решение» (оно заставляет мои тесты пройти, и console.logs появляются в правильном порядке, но я не понимаю, почему это работает. Решение состоит в том, чтобы добавить строку await (() => new Promise(setImmediate))(); в тесте файл сразу после строки с await renderer.create.

** Итак, я не хочу только решение (хотя, если у вас есть идеальное решение, пожалуйста, предоставьте его). Я хочу знать, что здесь происходит, почему оригинальный код не работает должным образом? **

1 Ответ

1 голос
/ 20 мая 2019

async / await - это просто синтаксический сахар для Обещаний и генераторов.

Когда вы вызываете await, вы по существу ставите в очередь остальную часть функции в then, присоединенном к ожидаемому Promise.

Это означает, что при разрешении Promise остальная часть функции добавляется в очередь PromiseJobs .

Promise обратные вызовы в очереди PromiseJobs запускаются после завершения текущего сообщения ..., что означает, что любой синхронный код завершится до того, как обратный вызов сможет запустить.

В этом случае эта строка запускается:

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

Это поставит в очередь оставшуюся часть теста в конце очереди 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!
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...