Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest testing hook state update with setTimeout

I'm trying to test unmount of a self-destructing component with a click handler. The click handler updates useState using a setTimeout.

However, my test fails whereas I'm expecting it to pass. I tried using jest mock timers such as advanceTimersByTime() but it does not work. If I call setState outside setTimeout, the test passes.

component.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}
    </>
);

};

export default DangerMsg;

test.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);
});

Note, I'm mocking useState implementation with actual because in other tests I used custom useState mock.

like image 217
cjm Avatar asked Dec 18 '22 13:12

cjm


1 Answers

Not using Enzyme but testing-library/react instead so it's a partial solution. The following test is passing as expected:

  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();
  });

I directly run timers but advancing by time should give similar results.

like image 118
Eric Burel Avatar answered Dec 31 '22 03:12

Eric Burel