react-hooks-testing-library version: 3.7.0react-test-renderer version: 17.0.1react version: 17.0.1node version: v12.16.2npm (or yarn) version: 6.14.8import { useState, useCallback } from 'react';
export default function useAsync() {
const [count, setCount] = useState(0);
const increment = useCallback(() => setCount(x => x + 1), []);
const incrementAsync = useCallback(() => setTimeout(increment, 200), [increment]);
return {
count,
incrementAsync,
};
}
// test file
import { renderHook } from '@testing-library/react-hooks';
import useAsync from './useAsync';
test('should increment counter after delay', async () => {
const { result, waitForNextUpdate } = renderHook(() => useAsync());
result.current.incrementAsync(); // async不需要放在act里
await waitForNextUpdate();
expect(result.current.count).toBe(1);
});
I run jest --watch
jest keeps running this test and never stops. you can see it from the screenshot, it's been running for 620s.
https://github.com/thundersdata-frontend/rn-template

Hi @chj-damon,
Firstly, you're updating the state of the component, so don't forget to wrap that call in act like so -
act(() => result.current.incrementAsync()
otherwise you'll get an error.
Secondly, if you're using jest.useFakeTimers and you want to make sure those timers run (like you do in your test) you need to manually run them see here & here. Now because of this, you'd need to run jest.runAllTimers() inside act. But also because you're mocking all these timers await waitForNextUpdate will never fire because there's no async behaviour. So to get your test to pass you either want to remove jest.useFakeTimers or write it like this –
import { act, renderHook } from '@testing-library/react-hooks';
import useAsync from './useAsync';
test('should increment counter after delay', async () => {
const { result } = renderHook(() => useAsync());
act(() => {
result.current.incrementAsync();
jest.runAllTimers();
}); // async不需要放在act里
await expect(result.current.count).toBe(1);
});
@mpeyper I'm wondering if we need to do something to highlight this caveat when using fakeTimers? Or impliment a work around... what do you think?
@joshuaellis thanks for you reply. But I noticed that the document says that I don't need to wrap async functions inside act:
https://react-hooks-testing-library.com/usage/advanced-hooks#async

And I agree that the document should be more precise about timers because you have to config jest.useFakeTimers(); to be able to mock functions like setTimeout / setInterval
@joshuaellis and also, I tried the code above, it doesn't work, unfortunately.
But I noticed that the document says that I don't need to wrap async functions inside act
sorry my bad, I wrote the whole comment after fixing it.
RE. code, are you sure? I've got your repo & this works fine –

Yes, I think some documentation around this is a good idea. The the async utils are designed for situations where you don't know when the update will occur but by using fake timers you are taking control of time and therefore have no need to wait.
I've got a memory of seeing somewhere a way to ask jest if fake timers are in use, but I can't find anything in their docs or on google about it now. If such a thing exists, we could put a warning in waitForNextUpdate if they are set and you try waiting. There are possible some legitimate case where you need both fake timers and an arbitrary wait time, but I don't think it would be common.
I noticed that the document says that I don't need to wrap async functions inside
act
You technically don't need to act around this one because setCount happens in the timeout callback, which in the documentation example will be acting thanks to waitForNextUpdate. If you had a setState in the function that starts the timeout as well as in the timeout callback, then you would need the act as well.
For your example, this should be fine as well:
result.current.incrementAsync();
act(() => {
jest.runAllTimers();
});
Now when the update occurs , it is within the act block.
@joshuaellis why await waitForNextUpdate() is not necessary now?
why await waitForNextUpdate() is not necessary now?
because you are advancing the timer passed the point of updating and now there is no update to wait for
@mpeyper OK I understand now. If I remove jest.useFakeTimers() from 'setupFiles' in jest config. the example on the doc works perfectly. If I add jest.useFakeTimers(), I don't need await waitForNextUpdate(), just use jest. runAllTimers()
Thank you guys so much for the explanation!
Most helpful comment
@mpeyper OK I understand now. If I remove
jest.useFakeTimers()from 'setupFiles' in jest config. the example on the doc works perfectly. If I addjest.useFakeTimers(), I don't needawait waitForNextUpdate(), just usejest. runAllTimers()