WaitForNextUpdate of renderHook of react-testing-library timeout
Asked Answered
S

2

15

I'm testing a custom hook with react-testing-library which basically does this:

function useHook() {
  const [state, setState] = useState();
  
  const fetch = async () => {
    const response = await httpCall();
    if (instanceof response !== Error) {
      setState("GOOD")
    } else {
      setState("BAD")
    }
  }
  
  return { state, fetch }
}

and my test file is something like this:

it("test", async () => {
  const { result, waitForNextUpdate } = renderHooks(() => useHook())

  await result.current.fetch();
  expect(result.current.state).toBe(undefined)

  await waitForNextUpdate();
  expect(result.current.state).toBe("GOOD") //or at least "BAD"
})

I wrote this because I called the async function fetch() that should trigger the setState, I assert that no rerender has been occurred yet, and then I waitForNextUpdate() in order to wait this rerender and I assert that the state returned by the hooks has now a value "GOOD" or "BAD".

My problem is that my test gives me an error: Timeout - Async callback was not invoked within the 5000 ms ..., and this error occurred when the test waits for the waitForNextUpdate().

I don't know what's wrong with my test. I'm sure (because i tested it) that the hook is working properly, the http call has been made. I know that checking values inside the test but also because the hook is working properly inside the application. I don't understand why it seems that the update of the state never occures.

I'm the first one of my team who is testing with this tool so i'm quite lost.

Streptomycin answered 11/2, 2022 at 17:27 Comment(0)
W
4

First of all you have small mistake in if statment of the hook, so let's correct that and also add import of httpCall function for example sake

import { useState } from 'react'
import { httpCall } from './httpCall'

export function useHook() {
  const [state, setState] = useState<string>()

  const fetch = async () => {
    const response = await httpCall()
    if (response instanceof Error) {
      setState('GOOD')
    } else {
      setState('BAD')
    }
  }

  return { state, fetch }
}

now we can have two test cases, based on mocking httpCall results. Note that rerender is used instead of waitForNextUpdate

import { waitFor } from '@testing-library/react'
import { renderHook } from '@testing-library/react-hooks'
import { httpCall } from './httpCall'
import { useHook } from './useHook'

jest.mock('./httpCall', () => ({
  httpCall: jest.fn()
}))

describe('useHook', () => {
  it('should return error', async () => {
    // httpCall was mocked above, so we can replace it's implementation
    httpCall.mockImplementation(() => new Error('error'))
    const { result, rerender } = renderHook(() => useHook())

    await result.current.fetch()
    rerender()

    await waitFor(() => {
      expect(result.current.state).toBe('BAD')
    })
  })

  it('should return response', async () => {
    httpCall.mockImplementation(() => ({
      ok: true
    }))

    const { result, rerender } = renderHook(() => useHook())

    await result.current.fetch()
    rerender()

    await waitFor(() => {
      expect(result.current.state).toBe('GOOD')
    })
  })
})

Wilburwilburn answered 29/12, 2022 at 10:42 Comment(0)
M
1

You should not use await when calling the function under test of your hook.

It should instead be wrapped in act(), though as specified in the documentation about async testing it can be omitted when await waitForNextUpdate() is called.

The test passes with this change :

it('test', async () => {
  const { result, waitForNextUpdate } = renderHook(() => useHook());

  act(() => {
    result.current.fetch();
  });
  expect(result.current.state).toBe(undefined);

  await waitForNextUpdate();
  expect(result.current.state).toBe('GOOD'); //or at least "BAD"
});
Mocha answered 28/3, 2022 at 6:41 Comment(1)
Your answer is missing import statements, which are super important here, because there are several different react testing libraries that have a renderHook function. However, not all of those renderHook versions return the waitForNextUpdate function.Palladium

© 2022 - 2024 — McMap. All rights reserved.