Библиотека тестирования реакции — не завернута в ошибку act()

#javascript #reactjs #async-await #jestjs #react-testing-library

Вопрос:

У меня возникли проблемы с использованием библиотеки тестирования реакции для тестирования компонента переключения.

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

Однако событие щелчка, похоже, не работает, и я получаю следующую ошибку:

 > jest "MyFile.spec.tsx"

 FAIL  src/my/path/__tests__/MyFile.spec.tsx
  component MyFile
    ✓ renders when opened (94 ms)
    ✓ renders with items (33 ms)
    ✕ toggles verification status on click of icon button (100 ms)


  console.error
    Warning: An update to MyFile inside a test was not wrapped in act(...).
    
    When testing, code that causes React state updates should be wrapped into act(...):
    
    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */
    
    This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
        at MyFile (/path/to/myfile.tsx:44:3)
        at ThemeProvider (/users/node_modules/@material-ui/styles/ThemeProvider/ThemeProvider.js:48:24)

      123 |       );
      124 |     } finally {
    > 125 |       setIsLoading(false);
          |       ^
      126 |     }
      127 |   };
      128 |

      at printWarning (node_modules/react-dom/cjs/react-dom.development.js:67:30)
      at error (node_modules/react-dom/cjs/react-dom.development.js:43:5)
      at warnIfNotCurrentlyActingUpdatesInDEV (node_modules/react-dom/cjs/react-dom.development.js:24064:9)
      at dispatchAction (node_modules/react-dom/cjs/react-dom.development.js:16135:9)
      at handleConfirm (src/modules/myfile.tsx:125:7)
 

В моем коде у меня есть такая функция:

 const handleSubmit = async() => {
  if(isLoading) {
    return;
  }

  try {
    setIsLoading(true);
    await myFunctionCalls();
  } catch (error){
    console.log(error)
  } finally {
    setIsLoading(false)
  }
};
 

Мой тест выглядит примерно так:

 test('toggles verification status on click of icon button', async () => {
    renderWithTheme(
    <MyComponent/>,
   );

  const updateVerificationMock = jest.fn();
  const callFunctionWithSerializedPayloadMock =
    callFunctionWithSerializedPayload as jest.Mock;
  callFunctionWithSerializedPayloadMock.mockImplementation(
    () => updateVerificationMock,
  );

    const button = screen.getByRole('button', {name: 'Remove approval'});
    fireEvent.click(button);

    await act(async () => {
      expect(myFunctionCalls).toHaveBeenCalledTimes(1);
    });
    expect(await screen.findByText('unverified')).toBeInTheDocument();
  });
 

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

Я знаю, что обычно ошибка act связана с асинхронностью/ожиданием вызовов, но я подумал, что findByText должен подождать, и, похоже, есть еще одна проблема, которую я здесь не улавливаю. Любая помощь в том, что нужно сделать для отладки/улучшения этого теста?

Ответ №1:

Есть 3 асинхронные функции, которые вызываются здесь при нажатии на Remove Approval кнопку.

Сначала вы устанавливаете состояние загрузки в значение true, поэтому оно загрузится, затем вызывается асинхронная функция (myFunctionCalls), и, наконец, загрузчик исчезнет после установки состояния загрузки в значение false.

Чтобы решить эту проблему, мы должны сначала дождаться появления загрузки, затем вызвать myFunctionCalls, а затем дождаться исчезновения загрузки.

 test("toggles verification status on click of icon button", async () => {
  renderWithTheme(<MyComponent />);

  const updateVerificationMock = jest.fn();
  const callFunctionWithSerializedPayloadMock =
    callFunctionWithSerializedPayload as jest.Mock;
  callFunctionWithSerializedPayloadMock.mockImplementation(
    () => updateVerificationMock
  );

  const button = screen.getByRole("button", { name: "Remove approval" });
  fireEvent.click(button);

  expect(await screen.findByText(/loading/i)).toBeInTheDocument();

  await waitFor(() => {
    expect(myFunctionCalls).toHaveBeenCalledTimes(1);
  });

  await waitForTheElementToBeRemoved(() => {
    expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
  });


  expect(await screen.findByText("unverified")).toBeInTheDocument();
});

 

Если у вас нет загружаемого текста, вы можете использовать act(() => jest.advanceTimersByTime(500)); его для продления времени до 500 мс. Когда время достигнет 500 мс, функция асинхронности будет решена.

 beforeEach(() => {
  jest.useFakeTimers();
})

afterEach(() => {
  jest.runAllPendingTimers();
  jest.useRealTimers()
})

test("toggles verification status on click of icon button", async () => {
  renderWithTheme(<MyComponent />);

  const updateVerificationMock = jest.fn();
  const callFunctionWithSerializedPayloadMock =
    callFunctionWithSerializedPayload as jest.Mock;
  callFunctionWithSerializedPayloadMock.mockImplementation(
    () => updateVerificationMock
  );

  const button = screen.getByRole("button", { name: "Remove approval" });
  fireEvent.click(button);

  act(() => jest.advanceTimersByTime(500));

  await waitFor(() => {
    expect(myFunctionCalls).toHaveBeenCalledTimes(1);
  });

  act(() => jest.advanceTimersByTime(500));

  expect(await screen.findByText("unverified")).toBeInTheDocument();
});