75

I have this test:

import {
  render,
  cleanup,
  waitForElement
} from '@testing-library/react'

const TestApp = () => {
  const { loading, data, error } = useFetch<Person>('https://example.com', { onMount: true });

  return (
    <>
      {loading && <div data-testid="loading">loading...</div>}
      {error && <div data-testid="error">{error.message}</div>}
      {data && 
        <div>
          <div data-testid="person-name">{data.name}</div>
          <div data-testid="person-age">{data.age}</div>
        </div>
      }
    </>
  );
};

  describe("useFetch", () => {
    const renderComponent = () => render(<TestApp/>);

    it('should be initially loading', () => {
      const { getByTestId } = renderComponent();

      expect(getByTestId('loading')).toBeDefined();
    })
  });

The test passes but I get the following warning:

Warning: An update to TestApp 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
    in TestApp

console.error node_modules/react-dom/cjs/react-dom.development.js:506 Warning: An update to TestApp 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
    in TestApp
dagda1
  • 26,856
  • 59
  • 237
  • 450

8 Answers8

92

The key is to await act and then use async arrow function.

await act( async () => render(<TestApp/>));

Source:

https://stackoverflow.com/a/59839513/3850405

Ogglas
  • 62,132
  • 37
  • 328
  • 418
48

Try asserting inside 'await waitFor()' - for this your it() function should be async

it('should be initially loading', async () => {
  const { getByTestId } = renderComponent();

  await waitFor(() => {
    expect(getByTestId('loading')).toBeDefined();
  });
});

Keep calm and happy coding

sugaith
  • 772
  • 7
  • 14
5

Try using await inside act

import { act } from 'react-dom/test-utils';
await act(async () => {
            wrapper = mount(Commponent);
            wrapper.find('button').simulate('click');
        });
Piyush Sarin
  • 133
  • 1
  • 5
  • This worked for me, though I'm not really sure why? The example here is very similar to my code, there is nothing awaited in the inner function, and we aren't returning any other promise, so it seems like it should just be a promise that returns immediately. I guess it just waits until the next tick, which is enough? – Robin Clowers Mar 31 '23 at 03:52
4

I was getting the same issue which gets resolved by using async queries (findBy*) instead of getBy* or queryBy*.

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

Async query returns a Promise instead of element, which resolves when an element is found which matches the given query. The promise is rejected if no element is found or if more than one element is found after a default timeout of 1000ms. If you need to find more than one element, use findAllBy.

https://testing-library.com/docs/dom-testing-library/api-async/

But as you know it wont work properly if something is not on screen. So for queryBy* one might need to update test case accordingly

[Note: Here there is no user event just simple render so findBy will work otherwise we need to put user Event in act ]

paraS elixiR
  • 1,866
  • 1
  • 18
  • 26
1
    test('handles server ok', async () => {
    render(
      <MemoryRouter>
        <Login />
      </MemoryRouter>
    )

    await waitFor(() => fireEvent.click(screen.getByRole('register')))

    let domInfo
    await waitFor(() => (domInfo = screen.getByRole('infoOk')))

    // expect(domInfo).toHaveTextContent('登陆成功')
  })

I solved the problem in this way,you can try it.

moderate
  • 11
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Mar 28 '22 at 07:41
1

This is just a warning in react-testing-library (RTL). you do not have to use act in RTL because it is already using it behind the scenes. If you are not using RTL, you have to use act

import {act} from "react-dom/test-utils"
test('',{
    act(()=>{
        render(<TestApp/>)
    })
})

You will see that warning when your component does data fetching. Because data fetching is async, when you render the component inside act(), behing the scene all the data fetching and state update will be completed first and then act() will finish. So you will be rendering the component, with the latest state update

Easiest way to get rid of this warning in RTL, you should run async query functions findBy*

test("test", async () => {
  render(
    <MemoryRouter>
      <TestApp />
    </MemoryRouter>
  );

  await screen.findByRole("button");
});
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
0

I don't see the stack of the act error, but I guess, it is triggered by the end of the loading when this causes to change the TestApp state to change and rerender after the test finished. So waiting for the loading to disappear at the end of the test should solve this issue.

describe("useFetch", () => {
  const renderComponent = () => render(<TestApp/>);

  it('should be initially loading', async () => {
    const { getByTestId } = renderComponent();

    expect(getByTestId('loading')).toBeDefined();
    await waitForElementToBeRemoved(() => queryByTestId('loading'));
  });
});
Vizor
  • 396
  • 3
  • 14
0

React app with react testing library:

I tried a lot of things, what worked for me was to wait for something after the fireevent so that nothing happens after the test is finished.

In my case it was a calendar that opened when the input field got focus. I fireed the focus event and checked that the resulting focus event occured and finished the test. I think maybe that the calendar opened after my test was finished but before the system was done, and that triggered the warning. Waiting for the calendar to show before finishing did the trick.

fireEvent.focus(inputElement);

await waitFor(async () => {
  expect(await screen.findByText('December 2022')).not.toBeNull();
});
expect(onFocusJestFunction).toHaveBeenCalledTimes(1);
// End

Hopes this helps someone, I just spent half a day on this.

Tor Thorbergsen
  • 684
  • 2
  • 6
  • 14