Search code examples
reactjsjestjsmockingjest-fetch-mock

How do I avoid wrap test in act(() =>{} warnings when trying to mock multiple API with jest-fetch-mocks at once in jest?


I am trying to write unit tests for a react application that calls multiple static API endpoints to populate a bunch of different dropdown element options.

A stripped down version of my react component is below. I have made a custom hook useFetchReferenceData that takes care of calling all the reference APIs and setting the dropdown options.

export function SearchSubmitterProfile() {
  const [search, setSearch] = useState(true);
  const [viewResults, setViewResults] = useState(false);
  const [createSubmitterProfileState, setCreateSubmitterProfileState] = useState(INITIAL_STATE);

  useFetchReferenceData(
    createSubmitterProfileState,
    setCreateSubmitterProfileState
  );

  return (
    <div id="search-sub-profile" key="search-sub-profile" className={styles.grayBg}>
      {search ? (
        <ConnectedSearchView
        />
      ) : null}
      {viewResults ? (
        <ConnectedSubmitterProfile
        />
      ) : null}
    </div>
  );
}

export default SearchSubmitterProfile;

In useFetchReferenceData, I have about 14 of the below fetch calls and dropdown builder functions like the below. Each of these functions is called in an asynchronous useEffect at the bottom of the custom hook file

  const { run: getLanguageIndicators } = useFetchye(`${subApiUrl}/submlanguageind`, {
    credentials: 'include',
    headers: getDefaultHeaders(),
    defer: true,
  });

  const buildLanguagIndDropdown = async () => {
    const currentState = { ...createSubmitterProfileState };
    const { data } = await getLanguageIndicators();
    if (data.status === 200) {
      const result = [];
      result.push(DROPDOWN_STARTER);
      data.body.map((element) => result.push({
        value: `${element.dialect_language_code}`,
        label: `${element.dialect_language_code} - ${element.dialect_name}`,
      }));
      currentState.staticFieldState.submitterLanguageIndicator.options = result;
      setCreateSubmitterProfileState(currentState);
    } else {
      errorList.push('Error retrieving language indicators');
    }
  };

  useEffect(() => {
    const fetchAllData = async () => {
      await buildRegionsDropDown();
      await buildLanguagIndDropdown();
      ... 14 more of these
    };
    fetchAllData().catch(console.error);
  }, []);

In my tests I am trying to mock all of these fetch calls to cover all of the lines in the useFetchReferenceData file. The test looks like the below, and I am using the jest-fetch-mock library to mock the series of API calls.

describe('Search Submitter Profile reference data', () => {
  beforeEach(() => {
    enableFetchMocks();
    fetch.mockResponses(
      [
        JSON.stringify(mockRegions),
        { status: 200 },
      ],
      [
        JSON.stringify(mockLanguageIndicators),
        { status: 200 },
      ],
      ... 14 more
    );
  });

  it('validate the search information view', async () => {
    render(
      <UserContext.Provider value={{ loggedInUser }}>
        <SearchSubmitterProfile />
      </UserContext.Provider>
    );
  });
});

The above test passes, but I get a bunch of the following warnings that I am wondering how to fix.

  console.error
    Warning: An update to SearchSubmitterProfile 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 */

Solution

  • Your test doesn't appear to test anything - it calls render but does nothing after that. Did you omit your expect calls from your code snippet?

    Anyway, the warnings are telling you that the fetch calls are finishing asynchronously, which triggers re-renders, and the test code doesn't approve of React re-rendering without knowing about. If you were triggering re-renders synchronously, by simulating user input, then you could wrap those user input events in act (see this example). Since they're occurring asynchronously, that won't work. You have a couple of alternatives:

      it('validate the search information view', async () => {
        render(
          <UserContext.Provider value={{ loggedInUser }}>
            <SearchSubmitterProfile />
          </UserContext.Provider>
        );
    
        await screen.findByText('something that only displays once all fetches finish');
        // Or
        await waitFor(() => expect(someComplexCriteria).toBeTruthy());
    
        // Continue with test assertions
      });