Jest - тестирование clearTimeout в хуке useEffect

В моем компоненте кнопки при нажатии я принудительно отключаю кнопку на 500 мс, чтобы предотвратить многократные отправки, а через 0,5 с отключенное состояние возвращается к значению по умолчанию. Несмотря на разные подходы, я получаю две строки кода, которые я не могу охватить в своем модульном тесте.

Пожалуйста, ознакомьтесь с упрощенным источником компонентов ниже:

import React, {useState, useEffect} from 'react';
const Button = ({disabled, onClick}) => {
    const [disableButton, forceDisabledButton] = useState(false);
    useEffect(() => {
        let timeId;
        if (disableButton) {
            timeId = setTimeout(() => {
                forceDisabledButton(false);
            }, 500);
        }
        return () => {
            if (timeId) {
                clearTimeout(timeId);
            }
        }
    }, [disableButton]);
    const onButtonClick = (e) => {
        onClick && onClick(e);
        forceDisabledButton(true);
    }
    return (
        <button onClick={onButtonClick} disabled={!disableButton ? disabled : disableButton}>Button</button>
    )
}

Значение по умолчанию disabled установлен в false, Контрольный пример:

(...)
it('should become disabled after click and then return to its previous disabled state', () => {
    const mountButton = shallow(<Button/>);
    jest.useFakeTimers();
    expect(mountButton.find('button').length).toEqual(1);
    mountButton.find('button').simulate('click');
    expect(mountButton.find('button').prop('disabled')).toEqual(true);
    setTimeout(() => {
        expect(mountButton.find('button').prop('disabled')).toEqual(false);
        expect(clearTimeout).toHaveBeenCalledWith(expect.any(Number));
    }, 600)
})

Линии, которые не покрываются: forceDisabledButton(false); а также clearTimeout(timeId);, Я пытался jest.runAllTimers() Первоначально, но он также не смог покрыть эти две функции. Тест проходит, и в приложении у меня нет никаких предупреждений об утечке памяти (и визуального подтверждения того, что кнопка отключается на 500 мс), поэтому я знаю, что она работает нормально, и обе эти функции вызываются. Какие модификации я могу попытаться применить к этим двум функциям в моем модульном тесте?

Спасибо

1 ответ

Вы можете использовать runAllTimers:

it('should become disabled after click and then return to its previous disabled state', (done) => {
    const mountButton = mount(<Button/>);
    jest.useFakeTimers();
    expect(mountButton.find('button').length).toEqual(1);
    mountButton.find('button').simulate('click');
    expect(mountButton.find('button').prop('disabled')).toEqual(true);
    setTimeout(() => {
        expect(mountButton.find('button').prop('disabled')).toEqual(false);
        done(); // not sure if it's required for case with `runAllTimers`
    }, 600);
    jest.runAllTimers();
})

Или вы можете использовать advanceTimersByTime что вы можете проверить, если задержка точно 500:

it('should become disabled after click and then return to its previous disabled state', () => {
    const mountButton = mount(<Button/>);
    jest.useFakeTimers();
    // ...
    jest.advanceTimersByTime(499);
    expect(mountButton.find('button').prop('disabled')).toEqual(true);
    jest.advanceTimersByTime(2);
    expect(mountButton.find('button').prop('disabled')).toEqual(false);
})

Что касается clearTimeout как часть очистки для useEffect он будет вызываться либо при повторном рендеринге, либо при монтировании. Так что если вы действительно хотите проверить, называется ли это, просто запустите повторный рендеринг с mountButton.update(), Но вы можете просто проверить, если clearTimeout скорее проверьте, был ли он вызван как часть useEffect крюк.

в общем, безопаснее в использовании runOnlyPendingTimers над runAllTimers так как позже однажды может вызвать бесконечный цикл, если мы имеем последовательный setTimeout в useEffect(но не в этом случае)

[UPD] shallow() может не сработать, так как есть проблемы с интеграцией с хуками.

Другие вопросы по тегам