开玩笑-在使用中测试clearTimeoutEffect挂钩

时间:2019-06-28 13:26:09

标签: reactjs jestjs react-hooks use-effect

在单击的按钮组件中,我强制禁用按钮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(),但也未能涵盖这两个功能。测试通过了,并且在应用程序中,我没有任何内存泄漏警告(也没有视觉确认该按钮将被禁用500ms),因此我知道它可以正常工作,并且这两个函数都被调用了。在单元测试中,我可以尝试修改哪些功能来解决这两个功能?

谢谢

1 个答案:

答案 0 :(得分:0)

您可以使用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 cleanout 的一部分,它将在重新渲染或挂载时被调用。因此,如果您真的想检查它是否被调用,只需使用mountButton.update()触发重新渲染。但是,您仅可以验证是否调用了clearTimeout,而是检查它是否已作为useEffect钩子的一部分被调用。

通常,在runOnlyPendingTimers上使用runAllTimers更为安全,因为如果我们在setTimeout中有连续的useEffect,那么以后一次可能会导致无限循环(但不是这种情况)

[UPD] shallow()可能无法正常工作,因为有still opened issues与钩子集成在一起。