testing-library / react-hooks-testing-library

🐏 Simple and complete React hooks testing utilities that encourage good testing practices.
https://react-hooks-testing-library.com
MIT License
5.25k stars 230 forks source link

Asyncs utils: You called act(async () => ...) without await #825

Open enzzoperez opened 2 years ago

enzzoperez commented 2 years ago

Hi!, Im starting to test my hooks (in a POC stage), I have a simple test that works but I get the following warning that I dont know how can it solved :thinking:

Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);

      at console.error (node_modules/@testing-library/react-hooks/lib/core/console.js:19:7)
      at printWarning (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:68:30)
      at error (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:44:5)
      at node_modules/react-test-renderer/cjs/react-test-renderer.development.js:15297:13
      at tryCallOne (node_modules/promise/lib/core.js:37:12)
      at node_modules/promise/lib/core.js:123:15
      at flush (node_modules/asap/raw.js:50:29)

this is my hook

import {useState, useEffect} from 'react';

const defaultValue = {data: 'Default'};

const useRemoteData = ({data}: any) => {
  const [response, setResponse] = useState<any>();
  const [error] = useState<any>();
  const [isLoading] = useState(true);

  useEffect(() => {
    data ? setTimeout(() => setResponse(data), 500) : setResponse(defaultValue);
  }, [data]);

  return {response, error, isLoading};
};

export default useRemoteData;

and in my test file, Im trying to test that if the props change, the data will change too

  import {renderHook} from '@testing-library/react-hooks';
  import useRemoteData from './useRemoteData';

  it('Init error false ', async () => {
    const {result, waitFor, rerender} = renderHook(
      ({data}) => useRemoteData({data}),
      {
        initialProps: {data: 'first render'},
      },
    );

    await waitFor(() => {
      expect(result.current.response).toEqual('first render');
    });

    rerender({data: 'second render'});

    await waitFor(() => {
      expect(result.current.response).toEqual('second render');
    });
  });

Am I forgetting something in the test file?

Thanks!

tcank commented 2 years ago

Same issue here! The problem comes when using more than one awaited waitFor in the same it Already read the act behavior in this PR https://github.com/callstack/react-native-testing-library/pull/969/files and tried all the suggestions of this issue https://github.com/callstack/react-native-testing-library/issues/379.

tcank commented 2 years ago

We can fix the problem using the solution proposed on https://stackoverflow.com/questions/64952449/react-native-testing-act-without-await/69201830#69201830 It seems that react-native jest preset is replacing global Promise implementation (2020 issue https://github.com/facebook/react-native/issues/29303) and this leads to the act-await warning.

The patch consists in add one previous preset and one post preset in order to save the original implementation and restore it after react-native preset is set. We do this and found that many tests started to fail (many), and in order to not debug each one, we change the patch to save the original implementation (in first preset) in global.nativePromise and polyfilled in global.polyfilledPromise (in last preset). Later, when we need to fix the Act Await warning, we swap the implementations only for a specific test suite calling fixActAwaitWarning:

const fixActAwaitWarning = (): void => {
  beforeAll(() => {
    global.Promise = global.nativePromise;
  });
  afterAll(() => {
    global.Promise = global.polyfilledPromise;
  });
};
mpeyper commented 2 years ago

Thank you for your effort and investigation into this @tcank.

farid-mitchi commented 2 years ago

We can fix the problem using the solution proposed on https://stackoverflow.com/questions/64952449/react-native-testing-act-without-await/69201830#69201830 It seems that react-native jest preset is replacing global Promise implementation (2020 issue facebook/react-native#29303) and this leads to the act-await warning.

The patch consists in add one previous preset and one post preset in order to save the original implementation and restore it after react-native preset is set. We do this and found that many tests started to fail (many), and in order to not debug each one, we change the patch to save the original implementation (in first preset) in global.nativePromise and polyfilled in global.polyfilledPromise (in last preset). Later, when we need to fix the Act Await warning, we swap the implementations only for a specific test suite calling fixActAwaitWarning:

const fixActAwaitWarning = (): void => {
  beforeAll(() => {
    global.Promise = global.nativePromise;
  });
  afterAll(() => {
    global.Promise = global.polyfilledPromise;
  });
};

Do you mean to change the content of restore-promise.js like this:

global.polyfilledPromise = Promise;

I found it really works!

robbporto commented 1 year ago

This is still happening. It only happens when I use more than one awaited waitFor in the same it.

faiwer commented 3 months ago

I came to this solution:

// https://github.com/testing-library/react-hooks-testing-library/issues/825
const asyncAct = (fn: () => Promise<unknown>) =>
  new Promise((resolve) => {
    act(fn).then(resolve);
  });

Using it instead of act avoid the warning.