I can't figure out why the callback passed to setTimeout is not called in my unit tests even after using jest.runAllTimers. The useEffect is called but the timeout callbacks aren't called and not covered as per Istanbul code coverage report as well.
The implementation is somewhat like this:
React Component:
const MyComponent = () => {
const [timer, setTimer] = useState(5);
useEffect(() => {
const timeout = setTimeout(() => {
console.log('***** Timer Ran!!! *********');
if(timer <= 5 && timer > 0) setTimer(timer - 1);
else {
return () => clearTimeout(timeout);
}
}, 1000);
}, [timer]);
<>
// some JSX
</>
};
Test:
jest.useFakeTimers(); // at top of file
it('should run code inside useEffect', () => {
const startBtn = screen.getByTestId('start-btn');
expect(startBtn).toBeEnabled();
// more code
jest.runAllTimers();
});
Note: I have tried wrapping jest.runAllTimers
in waitFor and act and it doesn't work.
My issue was resolved when I wrapped the rendering of the component with act
.
Sample code:
...
jest.useFakeTimers();
...
describe('Test Component', () => {
beforeEach(async () => {
await act(async () => {
render(<MyComponent />);
});
await act(async () => {
jest.runAllTimers();
});
});
afterEach(() => {
jest.resetAllMocks();
});
it('should check if ...', async () => {
// tests
});
});