How to use React-Testing-Library (RTL) to unit test a component with internal hook that uses ajax and state?

27 views Asked by At

The problem is that our Jest runs in jsdom and is quite old, but ultimately anytime I write a function like the below, Jest can't handle it when I try to use it in a component. The loading state stays as true. When I wrap it in console.logs I see the table state is never updated, loading stays true.

It's like I have to mock tableState/Dispatch, but it's internal to the component being tested, so there is no way of sending the mock in without being forced to disassociate the data from the from view. This can be a little counterproductive for lazy loading and prop passing issues, so trying to avoid lifting the hook.

const useTableDataBackend = (
  dataInit,
  RequestHandler = (OnSuccess, onError) => {},
  MutationHandler = (data, timezone) => {}
) => {        
  const [tableState, tableDispatch] = useReducer(dataInit)
            
  const { headers, filters, rowsPerPage, currentPage } = tableState
            
  const getPageRequestHandler = (resetPage = true) => {
    tableDispatch({ type: 'SET_LOADING', payload: true })
            
    RequestHandler(
      (data) => {
        tableDispatch({ type: 'SET_LOADING', payload: false })
      },
      () => {
        tableDispatch({ type: 'SET_LOADING', payload: false })
      }
    )

    useEffect(() => {
      tableDispatch({ type: 'SET_DATA', payload: dataInit })
      tableDispatch({ type: 'SET_DATA_PAGINATED', payload: dataInit })
    }, [dataInit])

    useEffect(() => {
      getPageRequestHandler(false)
    }, [currentPage])
  }

Use case:

const myComponent = () => {
  const RequestHandler = () => {
    // Implementation here
  }

  const MutationHandler = () => {
    // Implementation here
  }

  const { tableState, tableDispatch } = useTableDataBackend(
    MutationHandler(RequestHandler()),
    RequestHandler,
    MutationHandler
  )

  return (
    <div>{tableState}</div>
  )
}

Test Case:

jest.mock(
  'RequestHandler',
  () => jest.fn()
)

it('should paginate correctly onClick of each control', () => {
  RequestHandler.mockImplementationOnce(
    (OnSuccess, onError) => {
      //const data = Array.from({ length: 5 }, () => EncountersGetRequestHandlerSampleData.items.data[0])
      onSuccess(data)
    }
  )

  render(<myComponent />)
  // Expect loading to stop but never does, can't test a disabled paginator
})
1

There are 1 answers

0
gunslingor On

Your problem is here:

RequestHandler.mockImplementationOnce(
  (OnSuccess, onError) => {
    //const data = Array.from({ length: 5 }, () => EncountersGetRequestHandlerSampleData.items.data[0])
    onSuccess(data)
  }
)

Change to:

RequestHandler.mockImplementation(
      (OnSuccess, onError) => {
        //const data = Array.from({ length: 5 }, () => EncountersGetRequestHandlerSampleData.items.data[0])
        onSuccess(data)
      }
)

If the function being mocked needs to be called more than once to achieve the intended state, mockImplementationOnce will not work apparently.

Even on initialization for something as complex as backend custom pagination, the request could happen as filter variables and such are set. Perhaps that isn't ideal and the function should really only be called once, so the test perhaps uncovered something on init but this is still necessary.

Lesson Learnt: mockImplementation, not mockImplementationOnce, is required for anything beyond init dynamic testing of a component that uses a custom hook with state and ajax.