Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Testing Library - not wrapped in act() error

I'm having trouble using react-testing-library to test a toggle component.

On click of an icon (wrapped in a button component), I am expecting text to go from 'verified' to 'unverified'. In addition, a function is called where there are state updates.

However, the click event doesn't seem to work, and I am getting the below error:

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

In my code, I have a function like this:

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

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

My test looks similar to this:

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

The first expect passes as the function calls are called once, however I have the act() error from above, and also there is a failure as it seems that the text does not toggle from verified to unverified.

I am aware that usually the act error is an issue of async/waiting for calls to happen, but I thought that findByText should wait, and it seems like there is another issue I'm not catching here. Any help on what to do to debug/improve this test?

like image 626
stonerose036 Avatar asked Oct 20 '25 09:10

stonerose036


1 Answers

There are 3 async functions that are called here when you click on the Remove Approval button.

First, you are setting the loading state to true, so it will load then the async function (myFunctionCalls) is called, and finally, the loader will disappear after the loading state is set to false.

In order to solve it, we have to wait for the loading to appear first, then myFunctionCalls is called, and then later we have to wait for loading to disappear.

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

If you do not have loading text then you can use act(() => jest.advanceTimersByTime(500)); for extending the time till 500ms. When the time reaches 500ms, the async function would have been resolved.

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


like image 167
Subrato Pattanaik Avatar answered Oct 21 '25 23:10

Subrato Pattanaik



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!