Search code examples
reactjsunit-testingjestjsreact-testing-library

setTimeout callback is not being called in UNIT TESTS even after using fakeTimer and runAllTimers


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.


Solution

  • 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
      });
    });