Как я должен проверить React Hook "useEffect", делая вызов API с Typescript? - PullRequest
8 голосов
/ 28 марта 2019

Я пишу некоторые jest-ферментные тесты для простого приложения React с использованием Typescript и новых обработчиков React.

Однако я не могу должным образом смоделировать вызов API, выполняемый внутри useEffect крючок

useEffect выполняет вызов API и обновляет состояние useState «данные» с помощью «setData».

Объект «данные» затем сопоставляется в таблицу с соответствующими ячейками таблицы.

Кажется, что это должно быть легко для решения с поддельным ответом API и монтированием фермента, но япостоянно получаю сообщения об ошибках act() для обновления компонентов.

Я пытался использовать act() многими способами, но безрезультатно.Я попытался заменить axios на fetch и использовать мелкий фермент и рендер библиотеки рэп-теста, но, похоже, ничего не работает.

Компонент:

import axios from 'axios'
import React, { useEffect, useState } from 'react';

interface ISUB {
  id: number;
  mediaType: {
    digital: boolean;
    print: boolean;
  };
  monthlyPayment: {
    digital: boolean;
    print: boolean;
  };
  singleIssue: {
    digital: boolean;
    print: boolean;
  };
  subscription: {
    digital: boolean;
    print: boolean;
  };
  title: string;
}

interface IDATA extends Array<ISUB> {}

const initData: IDATA = [];

const SalesPlanTable = () => {
  const [data, setData] = useState(initData);
  useEffect(() => {
    axios
      .get(`/path/to/api`)
      .then(res => {
        setData(res.data.results);
      })
      .catch(error => console.log(error));
  }, []);

  const renderTableRows = () => {
    return data.map((i: ISUB, k: number) => (
      <tr key={k}>
        <td>{i.id}</td>
        <td>
          {i.title}
        </td>
        <td>
          {i.subscription.print}
          {i.mediaType.digital}
        </td>
        <td>
          {i.monthlyPayment.print}
          {i.monthlyPayment.digital}
        </td>
        <td>
          {i.singleIssue.print}
          {i.singleIssue.digital}
        </td>
        <td>
          <button>Submit</button>
        </td>
      </tr>
    ));
  };

  return (
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Name</th>
          <th>MediaType</th>
          <th>MonthlyPayment</th>
          <th>SingleIssue</th>
          <th/>
        </tr>
      </thead>
      <tbody'>{renderTableRows()}</tbody>
    </table>
  );
};

export default SalesPlanTable;

Тест:

const response = {
  data: {
    results: [
      {
        id: 249,
        mediaType: {
          digital: true,
          print: true
        },
        monthlyPayment: {
          digital: true,
          print: true
        },
        singleIssue: {
          digital: true,
          print: true
        },
        subscription: {
          digital: true,
          print: true
        },
        title: 'ELLE'
      }
    ]
  }
};

//after describe

it('should render a proper table data', () => {
    const mock = new MockAdapter(axios);
    mock.onGet('/path/to/api').reply(200, response.data);
    act(() => {
      component = mount(<SalesPlanTable />);
    })
    console.log(component.debug())
  });

Я ожидаю, что он запишет HTML таблицы с отображенным разделом тела таблицы, я попробовал несколько асинхронных и различных способов насмешки axiosно я продолжаю получать только заголовки таблицы или сообщение: обновление SalesPlanTable внутри теста не было перенесено в act(...). Я искал решение в течение многих часов, но не могу найти ничего, что работает, поэтому я решил проверитьнаберись смелости и спроси здесь.

1 Ответ

6 голосов
/ 28 марта 2019

Здесь есть две проблемы


Асинхронный вызов setData

setData вызывается в Promise обратном вызове.

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

В этом случае текущее запущенное сообщение - это ваш тест, поэтому ваш тест завершается до того, как будет выполнен обратный вызов Promise, и setData не будет вызван до после вашего теста.

Вы можете исправить это, используя что-то вроде setImmediate, чтобы отложить ваши утверждения до тех пор, пока не будут запущены обратные вызовы в PromiseJobs.

Похоже, вам также нужно будет вызвать component.update(), чтобы повторно отрендерить компонент с новым состоянием. (Я предполагаю, что это потому, что изменение состояния происходит вне act, поскольку нет никакого способа обернуть этот код обратного вызова в act.)

Все вместе, рабочий тест выглядит так:

it('should render a proper table data', done => {
  const mock = new MockAdapter(axios);
  mock.onGet('/path/to/api').reply(200, response.data);
  const component = mount(<SalesPlanTable />);
  setImmediate(() => {
    component.update();
    console.log(component.debug());
    done();
  });
});

Предупреждение. Обновление до ... не было включено в действие (...)

Предупреждение вызывается обновлениями состояния компонента, которые происходят за пределами act.

Изменения состояния, вызванные асинхронными вызовами setData, вызванными useEffect функцией , всегда будут происходить за пределами act.

Вот чрезвычайно простой тест, демонстрирующий это поведение:

import React, { useState, useEffect } from 'react';
import { mount } from 'enzyme';

const SimpleComponent = () => {
  const [data, setData] = useState('initial');

  useEffect(() => {
    setImmediate(() => setData('updated'));
  }, []);

  return (<div>{data}</div>);
};

test('SimpleComponent', done => {
  const wrapper = mount(<SimpleComponent/>);
  setImmediate(done);
});

Когда я искал дополнительную информацию, я наткнулся на enzyme выпуск # 2073 , открывшийся 10 часов назад и рассказавший об этом же поведении.

Я добавил вышеуказанный тест в комментарии , чтобы помочь разработчикам enzyme решить эту проблему.

...