Как мне протестировать React Hook «useEffect», выполняющий вызов API с помощью Typescript?

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

Однако я не могу правильно смоделировать вызов API внутри хука useEffect.

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

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

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

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

Компонент:

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.info(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.info(component.debug())
  });

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

so I decided to muster up some courage and ask here ...мы не НАСТОЛЬКО страшные, правда? :) (отличный вопрос, кстати)
Brian Adams 28.03.2019 04:16

Ха-ха, я кое-что спрашивал здесь некоторое время назад, но я был менее опытен в javascript и не искал здесь тщательно, прежде чем спрашивать, поэтому он был немедленно удален :(

Matt Berg 28.03.2019 16:53
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
35
2
31 256
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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


Асинхронный вызов 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.info(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 разработчикам решить проблему.

СПАСИБО БОЛЬШОЕ!! У него все еще есть странное предупреждение 'act()', как вы сказали, но теперь таблица фактически отображает правильные данные, и это все, что мне нужно... Снимаю шляпу перед вами, сэр!

Matt Berg 28.03.2019 18:09

Используйте jest.useFakeTimers и заверните jest.runAllImmediates в act. Таким образом, больше нет ошибки: github.com/eps1lon/react-act-immediate/blob/…. Однако я не смог решить исходную проблему с помощью этого метода.

epsilon 31.03.2019 20:14

Тест проходит, но все равно получает Warning: An update to Post inside a test was not wrapped in act(...).

Ben 03.04.2019 11:33

Мне удалось удалить предупреждение, обернув setImmediate в act вот так: await act(() => new Promise<void>((resolve) => { setImmediate(() => { node.update(); resolve(); }); }))

Arkanoid 28.10.2019 20:53

Решение

Это и работает, и избавляется предупреждения test was not wrapped in act(...).

const waitForComponentToPaint = async (wrapper) => {
   await act(async () => {
     await new Promise(resolve => setTimeout(resolve, 0));
     wrapper.update();
   });
};

Применение:

it('should do something', () => {
    const wrapper  = mount(<MyComponent ... />);
    await waitForComponentToPaint(wrapper);
    expect(wrapper).toBlah...
})

Благодаря...

Это обходной путь, предложенный эдпарк11 в проблема @Brian_Adams, упомянутом в его отвечать.

Исходное сообщение: https://github.com/enzymejs/enzyme/issues/2073#issuecomment-565736674

Я скопировал пост сюда с некоторыми изменениями для архивации.

Другие вопросы по теме