Skip to content

waitFor doesn't work if jest fake timers are used #631

Open
@DennisSkoko

Description

@DennisSkoko
  • react-hooks-testing-library version: 7.0.0
  • react version: 17.0.2
  • react-dom version: 17.0.2
  • node version: 14.16.0
  • npm version: 7.10.0

Problem

When using waitFor when Jest has been configured to use fake timers then the waitFor will not work and only "polls" once. After that the test just hangs until Jest comes in and fails the test with that the test exceeds the timeout time. Below is some code that showcases the problem.

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

it('does not work', async () => {
  jest.useFakeTimers()

  const { waitFor } = renderHook(() => {})

  await waitFor(() => {
    console.log('poll') // Is printed just once
    expect(false).toBe(true)
  }, { timeout: 25, interval: 10 })

  // Fails with Exceeded timeout of 5000 ms for a test.
})

Basically the waitFor from @testing-library/react-hooks is using the faked setTimeout or setInterval which prevents it from working correctly.

There is a workaround (see suggested solution) but I recommend providing a nice error message when waitFor is used together with faked timers or maybe change the implemenation so it will work with fake timers.

Suggested solution

I found this issue and it seems that person has already been fixed in @testing-library/dom. From my perspective I can suggest maybe reuse that function instead of implementing it yourselves but I don't really know the internal structure / code.

But after finding that issue and realizing that is has been fixed there, then I use the following code as a workaround which works fine.

import { waitFor } from '@testing-library/react'

it('works', async () => {
  jest.useFakeTimers()

  await waitFor(() => {
    console.log('poll') // Is printed twice
    expect(false).toBe(true)
  }, { timeout: 25, interval: 10 })

  // Fails with false is not equal to true
})

A more real world scenario

If curios on the actual problem I'm facing is to test the following hook:

function useSomething({ onSuccess }) {
  const poll = useCallback(async () => {
    const result = await fetch(/* ... */)
    if (result.ok) onSuccess()
  }, [onSuccess])

  useEffect(() => {
    const id = setInterval(() => { poll() }, 2000)
    return () => clearInterval(id)
  }, [poll])
}

What I want to do is test that it invokes the onSuccess function on a successfull poll.

it('invokes the `onSuccess` on successfull poll', async () => {
  const onSuccess = jest.fn()
  jest.useFakeTimers()

  const { waitFor } = renderHook(() => useSomething({ onSuccess }))

  jest.runOnlyPendingTimers()
  await waitFor(() => expect(onSuccess).toHaveBeenCalled())
})

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions