Search code examples
javascriptreactjsunit-testingjestjspreact

Testing a custom hook where useEffect should be invoked multiple times


I'm trying to test a custom React hook, where i have useEffect inside, and I want a reference to it in the tests as I have to call it multiple times to test the behavior. It's about simple throttling:

export const useThrottleEffect = (effect, deps = [], delay) => {
    const lastCall = useRef(0);

    useEffect(() => {
        const now = new Date().getTime();
        if (now - lastCall.current > delay) {
            effect();
            lastCall.current = now;
        }
    }, [...deps, delay]);
}

in the test, I have a basic scenario:

    let callback;

    beforeEach(() => {
        callback = jest.fn();
    });

    test("useThrottleEffect()", () => {
        const {result} = renderHook(() => useThrottleEffect(callback, [Date.now()], 1000));
        expect(callback).toHaveBeenCalledTimes(1);
        result; //?
        expect(result.current).toBeUndefined();
    });
});

But I what a reference to the inside useEffect in terms to be able to call multiple times and check the callback. Furthermore, a timer could tick and check the behavior. Any tips here would be highly appreciated!


Solution

  • You can create an utility component in your test, using the hook as intended to be used in your application.

    solution outline (not tested):

    import { render } from '@testing-library/react'
    
    const Util = ({ number, cb }) => {
      useThrottleEffect(cb, [], 2500)
    
      return number
    }
    
    describe('useThrottleEffect', () => {
      beforeAll(() => {
        jest.useFakeTimers()
      })
    
      it('should work', () => {
        const cb = jest.fn()
    
        const { rerender } = render(<Util cb={cb} number={1} />)
    
        jest.advanceTimersByTime(1000)
        rerender(<Util cb={cb} number={2} />)
    
        jest.advanceTimersByTime(1000)
        rerender(<Util cb={cb} number={3} />)
    
        jest.advanceTimersByTime(1000)
        rerender(<Util cb={cb} number={4} />)
    
        expect(cb).toHaveBeenCalledTimes(2)
      })
    })
    

    references: