Search code examples
reactjsjestjssettimeoutuse-effectreact-testing-library

React + Jest: Test that setTimeOut didn't get called


I currently have a setTimeOut inside a useEffect, I was able to test it by using:

jest.useFakeTimers();
jest.advanceTimersByTime(5000);

And it works great. However, the setTimeout is getting triggered a few times as successMessage changes. How can I write a test that checks that setTimeOut is only called when successMessage is not an empty string?

I'm using jest and react-testing-library.

Here is my react code.

enum HELPER_MESSAGES {
    SUCCESS = 'Congratulations you signed up!',
    ERROR = 'Email address invalid',
}

export function EmailCapture (): ReactElement {
    const [inputValue, setInputValue] = useState<string>('');
    const [errorMessage, setErrorMessage] = useState<string>('');
    const [successMessage, setSuccessMessage] = useState<string>('');

    function handleOnClick (): void {
        const EMAIL_REGEX: RegExp = /^[a-zA-Z0-9]+([-._][a-zA-Z0-9]+)*@([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*.)+\.+[a-zA-Z]{2,}$/;
        const isInputValid: boolean = EMAIL_REGEX.test(inputValue) && inputValue !== '';

        if (isInputValid) {
            setSuccessMessage(HELPER_MESSAGES.SUCCESS);
        } else {
            setErrorMessage(HELPER_MESSAGES.ERROR);
        }
    }

    useEffect((): () => void => {
        const resetAfterSuccessTimer: NodeJS.Timeout = setTimeout((): void => {
            setSuccessMessage('');
        }, 5000);

        return (): void => {
            clearTimeout(resetAfterSuccessTimer);
        };
    }, [successMessage]);

    function handleOnChange (e: ChangeEvent<HTMLInputElement>): void {
        setInputValue(e.target.value);
        setErrorMessage('');
        setSuccessMessage('');
    }

    return (
        <TextInput
            onChange={handleOnChange}
            error={errorMessage}
            success={successMessage}
            value={inputValue}
        />
    );
}

I'm following TDD, so I'm looking to write a test that will make me write the following code:

useEffect((): () => void => {
    let resetAfterSuccessTimer: NodeJS.Timeout;

    if (successMessage) {
        resetAfterSuccessTimer= setTimeout((): void => {
            setSuccessMessage('');
            setButtonText('Sign up!');
            console.log('sexy');
        }, 5000);
    }

    return (): void => {
        clearTimeout(resetAfterSuccessTimer);
    };
}, [successMessage]);

The sexy word should only get console logged once.


Solution

  • Figured it out based on their documentation: https://jestjs.io/docs/timer-mocks , you can just do the following:

    expect(setTimeout).not.toHaveBeenCalled();
    

    And when you want to check that it got called once, you can do:

    expect(setTimeout).toHaveBeenCalledTimes(1);
    

    Note: This is testing an implementation detail, so be aware of that.