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()
может не сработать, так как есть проблемы с интеграцией с хуками.