Description
react-hooks-testing-library
version: 7.0.0react
version: 17.0.2react-dom
version: 17.0.2node
version: 14.16.0npm
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())
})