使用 setTimeout 进行 Jest 测试挂钩状态更新

cjm*_*cjm 4 settimeout reactjs jestjs enzyme

我正在尝试使用单击处理程序测试自毁组件的卸载。单击处理程序使用 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}
    </>
);
Run Code Online (Sandbox Code Playgroud)

};

导出默认的 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);
});
Run Code Online (Sandbox Code Playgroud)

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

Eri*_*rel 11

不使用酶,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();
  });
Run Code Online (Sandbox Code Playgroud)

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

  • 仅供参考:“useFakeTimers”有一个可选参数“implementation”,用于在遗留实现(jest 自己的)和现代实现(sinon 的假计时器)之间进行选择。对于伪造计时器(例如 lodash debounce 使用的计时器),请使用“modern”。更多信息可以在[文档](https://jestjs.io/docs/en/jest-object#jestusefaketimersimplementation-modern--legacy)中找到。另外,在此[问题线程](https://github.com/facebook/jest/issues/3465#issuecomment-623393230)。 (2认同)