【问题标题】:Jest - testing clearTimeout in useEffect hook开玩笑 - 在 useEffect 挂钩中测试 clearTimeout
【发布时间】:2019-11-10 11:10:54
【问题描述】:

在单击按钮组件中,我强制禁用按钮 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 毫秒),所以我知道它工作正常并且这两个函数都被调用。我可以尝试哪些修改来解决我的单元测试中的这两个功能?

谢谢

【问题讨论】:

    标签: reactjs jestjs react-hooks use-effect


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

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

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

    【讨论】:

    • 令人惊讶的是,在尝试了您的两个建议后,测试不再通过(禁用不再更新为 false)。
    • 哦,我确实检查过了,但也许我使用了mount() 而不是shallow()。带有钩子的组件的浅层渲染仍然存在一些问题
    • github.com/airbnb/enzyme/issues/2011 - 根据浅渲染器现在根本没有运行useEffect hook
    猜你喜欢
    • 2020-10-27
    • 2021-10-21
    • 2020-08-30
    • 1970-01-01
    • 1970-01-01
    • 2020-02-06
    • 2020-03-25
    • 1970-01-01
    • 2021-11-17
    相关资源
    最近更新 更多