首页 > 解决方案 > 用 setTimeout 测试钩子状态更新

问题描述

我正在尝试使用单击处理程序测试自毁组件的卸载。点击处理程序使用 setTimeout 更新 useState。

但是,我的测试失败了,而我希望它通过。我尝试使用诸如 AdvanceTimersByTime() 之类的笑话模拟计时器,但它不起作用。如果我在 setTimeout 之外调用 setState,则测试通过。

组件.js

const DangerMsg = ({ duration, onAnimDurationEnd, children }) => {
const [isVisible, setVisible] = useState(false);
const [sectionClass, setSectionClass] = useState(classes.container);

function handleAnimation() {
    setSectionClass(classes.containerAnimated);
    let timer = setTimeout(() => {
        setVisible(false);
    }, 300);
    return clearTimeout(timer);
}

useEffect(() => {
    let timer1;
    let timer2;
    function animate() {
        if (onAnimDurationEnd) {
            setSectionClass(classes.containerAnimated);
            timer2 = setTimeout(() => {
                setVisible(false);
            }, 300);
        } else {
            setVisible(false);
        }
    }

    if (children) {
        setVisible(true);
    }
    if (duration) {
        timer1 = setTimeout(() => {
            animate();
        }, duration);
    }
    return () => {
        clearTimeout(timer1);
        clearTimeout(timer2);
    };
}, [children, duration, onAnimDurationEnd]);

return (
    <>
        {isVisible ? (
            <section className={sectionClass} data-test="danger-msg">
                <div className={classes.inner}>
                    {children}
                    <button
                        className={classes.btn}
                        onClick={() => handleAnimation()}
                        data-test="btn"
                    >
                        &times;
                    </button>
                </div>
            </section>
        ) : null}
    </>
);

};

导出默认的 DangerMsg;

测试.js

it("should NOT render on click", async () => {
    jest.useFakeTimers();
    const { useState } = jest.requireActual("react");
    useStateMock.mockImplementation(useState);
    // useEffect not called on shallow
    component = mount(
        <DangerMsg>
            <div></div>
        </DangerMsg>
    );
    const btn = findByTestAttr(component, "btn");
    btn.simulate("click");
    jest.advanceTimersByTime(400);
    const wrapper = findByTestAttr(component, "danger-msg");
    expect(wrapper).toHaveLength(0);
});

注意,我用实际来模拟 useState 实现,因为在其他测试中我使用了自定义 useState 模拟。

标签: reactjsjestjssettimeoutenzyme

解决方案


不使用酶,testing-library/react而是使用部分解决方案。以下测试按预期通过:

  test("display loader after 1 second", () => {
    jest.useFakeTimers(); // mock timers
    const { queryByRole } = render(
      <AssetsMap {...defaultProps} isLoading={true} />
    );
    act(() => {
      jest.runAllTimers(); // trigger setTimeout
    });
    const loader = queryByRole("progressbar");
    expect(loader).toBeTruthy();
  });

我直接运行计时器,但按时间推进应该会给出类似的结果。


推荐阅读