react-hooks-testing-library: Context Provider, state change in child function act() error

348 Views Asked by At

I am using the React renderHook() function to call a custom hook. Internally in the custom hook, I call a function that executes axios, gets data and on success, dispatches a state change to a custom context provider.

Upon execution I am getting the following error:

Warning: An update to StoreProvider inside a test was not wrapped in act(...).
      21 |              console.log('FetchUsers > RES: ', res.data[0])
      22 |              setLoading(false)
    > 23 |              dispatch({ 
         |              ^
      24 |                      type: ActionTypesEnum.FETCH_USERS, 
      25 |                      payload: res.data
      26 |              })

What should I be doing to make sure the error does not come up? Please see excerpts of the code below.

The useUserSearch Hook:

const useUserSearch = (searchCriteria: string): SearchHookProps => {
  // console.log('SEARCH: ', searchCriteria)
  // data from the context and stored in the state
  const { dispatch, state } = useStateContext();
  const { users } = state;
  const [ userData, setUserData ] = useState<UserProps[]>([]);
  const [ loading, setLoading ] = useState<boolean>(false);
  const [ error, setError ] = useState<boolean>(false);
  // connected to the pagination affected by the search
  const [ currentPage, setCurrentPage ] = useState<number>(1)
  const [ totalPages, setTotalPages ] = useState<number>(1)
  const [ totalUserCount, setTotalUserCount ] = useState<number>(0)
  const totalPerPage = 12

  useEffect(() => {
    if (users === null) FetchUsers(dispatch, searchCriteria, setLoading, setError);
  }, [users]);

  useEffect(() => {
      if (searchCriteria.length > 0) FetchUsers(dispatch, searchCriteria, setLoading, setError);
  }, [searchCriteria]);

  return {
    userData,
    totalUserCount,
    setCurrentPage,
    currentPage,
    totalPages,
    loading,
    error,
  }
}

FetchUsers function:

const FetchUsers = (
    dispatch: Dispatch<any>,
    searchCriteria: string,
    setLoading: Dispatch<SetStateAction<boolean>>,
    setError: Dispatch<SetStateAction<boolean>>,) => {
    const { global: { apiUrl } } = AppConfig

    const search = `?q=${encodeURIComponent(searchCriteria)}`

    setLoading(true)
    axios.get(`${apiUrl}/users?q=${search}+in:login+type:user`)
    .then((res) => {
        setLoading(false)
        dispatch({ 
            type: ActionTypesEnum.FETCH_USERS, 
            payload: res.data
        })
    })
    .catch(() => {
        setLoading(false)
        setError(true)
    })
}

StoreProvider:

import React, { useContext, useReducer, useMemo, createContext } from 'react';
import Reducer from '../../Reducers';
import AppConfig from '../../Configs';
import { StateInterface, StoreInterface, ProviderInterface } from '../../Interfaces';

export const initialState: StateInterface = {
  appConfig: AppConfig,
  users: null,
};

export const StoreContext = createContext({
  state: initialState,
} as StoreInterface);

export const useStateContext = () => useContext(StoreContext);

export const StoreProvider = ({ children }: ProviderInterface) => {
  const [state, dispatch] = useReducer(Reducer, initialState);
  const StoreProviderValue = useMemo(() => ({ state, dispatch }), [state, dispatch]);

  return <StoreContext.Provider value={StoreProviderValue}>{children}</StoreContext.Provider>;
};

The Test spec file:

import React from 'react'
import { render, cleanup, waitFor, renderHook, act } from '@testing-library/react'
import axios from 'axios'

import usersMock from '../__mocks__/usersMock'
import { StoreProvider } from '../Providers'
import useUserSearch from './useUserSearch'

jest.mock('axios')
const mockedAxios = axios as jest.Mocked<typeof axios>;

describe('Test that <User />', () => {
  const renderHookComponent = (searchCriteria: string) => {
    const wrapper = ({ children }: any) => <StoreProvider>{children}</StoreProvider>
    const { result, rerender, unmount } = renderHook(() => useUserSearch(searchCriteria), {
      wrapper: wrapper as React.ComponentType<any>
    })
    return { result, rerender, unmount }
  }

  afterEach(() => {
    cleanup()
  })

  it('renders the hook useUserSearch', async () => {
    mockedAxios.get.mockResolvedValue(usersMock);
    const { result } = renderHookComponent('test')

    // await waitFor(() => {
    //   expect(result.current.userData.length).toBe(0)
    // })
  })
})

I tried:

  1. Wrapping the FetchUsers function call inside the useEffect of the hook with (async()=>{...}).
  2. Taking off all "expect" test runs for that spec file.
  3. Researched into this online with numerous people having the same issue but should have been fixed (if a fix is needed).

Is there a way I can get it to play nice? Is my implementation correct? If not, how should I address it.

0

There are 0 best solutions below