简体   繁体   中英

When testing, code that causes React state updates should be wrapped into act

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

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

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

Source:

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

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

Try using await inside act

import { act } from 'react-dom/test-utils';
await act(async () => {
            wrapper = mount(Commponent);
            wrapper.find('button').simulate('click');
        });
    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.

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

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 ]

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.

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

I resolved this issue using below steps

  • Update react and react-dom to 16.9.0 version.
  • Install regenerator-runtime
  • import regenerator-runtime in setup file.
    import "regenerator-runtime/runtime";
    import { configure } from "enzyme";
    import Adapter from "enzyme-adapter-react-16";

    configure({
     adapter: new Adapter()
    });
  • Wrap mount and other possible actions which are going to cause state changes inside act. import act from simple react-dom/test-utils, async & await like below
    import React from 'react';
    import { mount } from 'enzyme';
    import App from './App';
    import { act } from "react-dom/test-utils";

    jest.mock('./api');

    import { fetchData } from './api';

    describe('<App />', () => {
    it('renders without crashing',  async (done) => {
      fetchData.mockImplementation(() => {
        return Promise.resolve(42);
      });
      await act(() => mount(<App />));
      setTimeout(() => {
        // expectations here
        done();
      }, 500);
     });  
    });

Hope this helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM