在 Jest/Enzyme 测试期间无法将 React Hook setInterval 计时器设置为 运行
Cannot Get React Hook setInterval Timer to Run During Jest/Enzyme Test
我正在尝试为该组件编写一些测试的总结。我在 Dan Abramov (code) 的博客 post 之后实现了一个自定义 useInterval
挂钩。它基本上是一个声明性的 setInterval
函数。但是,我很难用 Jest 和 Enzyme 对其进行测试。该应用程序是使用 create-react-app 引导的。我已经安装了最新版本。当我单击开始按钮时,经过的时间会增加并完美地显示在页面上。在父组件中,我更新存储经过时间的状态。它将更新后的经过时间 timer
道具传递给 Timer.js
。因此,它在屏幕上显示正确的 elapsedTime
。这在浏览器中按预期工作。但是,运行 测试时计时器不会 运行。
// Timer.js
const TimerDisplay = ({ timer, updateTimer }) => {
useInterval(() => {
const delta = Math.floor((Date.now() - timer.offsetTime) / 1000);
updateTimer({ ...timer, elapsedTime: delta });
}, timer.isTicking ? 300 : null);
const handleStartButton = () => {
updateTimer({ ...timer, isTicking: true });
}
return (
<React.Fragment>
<div>{timer.elapsedTime}</div>
<button className='start' onClick={() => handleStartButton()}>Start</button>
<button className='stop' {/* removed for brevity*/}>Stop</button>
</React.Fragment>
);
};
测试代码如下。我使用 Jest 的间谍功能和酶的坐骑。我读到我需要安装而不是因为挂钩而使用浅层。我将 Jest 设置为使用假定时器。然后,我模拟按下开始按钮以验证按钮是否正常工作。然而,在这个测试中,我已经设置了 isTicking: true
所以我什至不需要模拟启动。虽然它是一个完整性检查函数 spy 是否按预期工作 - 它确实如此。预期结果是它在 300 毫秒后调用间谍回调。因此,当我 jest.advanceTimersByTime(500)
时,间谍函数应该在 useInterval
回调中至少被调用一次。然而,这并没有发生在测试中。
// Timer.spec.js
describe('Timer', () => {
const spyFunction = jest.fn();
const timer = {
offsetTime: new Date(),
isTicking: true,
elapsedTime: 0
};
let wrapper = null;
beforeEach(() => {
wrapper = mount(<Timer updateTimer={spyFunction} timer={timer} />);
});
afterEach(() => {
wrapper.unmount();
wrapper = null;
});
it('timer should run', () => {
jest.useFakeTimers();
expect(spyFunction).not.toHaveBeenCalled();
wrapper.find('button.start').simulate('click', { button: 0 });
// works as expected
expect(spyFunction).toHaveBeenCalled();
// doesn't seem to work for some reason
jest.advanceTimersByTime(500);
// will fail; function only called once by the button.start. It should have been called at least twice.
expect(spyFunction).toHaveBeenCalledTimes(2);
});
});
我认为问题与 useInterval
挂钩有关。我怀疑在能够调用回调之前发生垃圾收集。有什么方法可以测试 useInterval
钩子的回调调用 updateTimer
又名 Jest.Fn
?
// useInterval hook
import { useEffect, useRef } from 'react';
export const useInterval = (callback, delay) => {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};
问题出在 jest.useFakeTimers();
,更具体地说是在您调用它的地方。在伪造计时器之前,您正在将组件安装在 beforeEach
中。因此,您的 useInterval
挂钩设置为使用真实的 setInterval
,而不是开玩笑的伪造。你在测试中调用 jest.useFakeTimers();
的那一刻为时已晚,因为它不会影响任何东西(你没有创建任何与计时器相关的新东西)。
如果您在 beforeEach
中的 mount
之前移动它,测试将起作用。
尝试做这样的事情
it("should run the count until you get to 00:00", () => {
loadWrapperAndInjectDependencies();
jest.useFakeTimers();
const resendOtpCode = wrapper.find({ testID: "buttonResendCode" });
resendOtpCode.simulate("press");
jest.runAllTimers();
const timeText = wrapper.find({ testID: "resendCodeTime" });
expect(timeText.props().children).toBe("00:00");
});
我正在尝试为该组件编写一些测试的总结。我在 Dan Abramov (code) 的博客 post 之后实现了一个自定义 useInterval
挂钩。它基本上是一个声明性的 setInterval
函数。但是,我很难用 Jest 和 Enzyme 对其进行测试。该应用程序是使用 create-react-app 引导的。我已经安装了最新版本。当我单击开始按钮时,经过的时间会增加并完美地显示在页面上。在父组件中,我更新存储经过时间的状态。它将更新后的经过时间 timer
道具传递给 Timer.js
。因此,它在屏幕上显示正确的 elapsedTime
。这在浏览器中按预期工作。但是,运行 测试时计时器不会 运行。
// Timer.js
const TimerDisplay = ({ timer, updateTimer }) => {
useInterval(() => {
const delta = Math.floor((Date.now() - timer.offsetTime) / 1000);
updateTimer({ ...timer, elapsedTime: delta });
}, timer.isTicking ? 300 : null);
const handleStartButton = () => {
updateTimer({ ...timer, isTicking: true });
}
return (
<React.Fragment>
<div>{timer.elapsedTime}</div>
<button className='start' onClick={() => handleStartButton()}>Start</button>
<button className='stop' {/* removed for brevity*/}>Stop</button>
</React.Fragment>
);
};
测试代码如下。我使用 Jest 的间谍功能和酶的坐骑。我读到我需要安装而不是因为挂钩而使用浅层。我将 Jest 设置为使用假定时器。然后,我模拟按下开始按钮以验证按钮是否正常工作。然而,在这个测试中,我已经设置了 isTicking: true
所以我什至不需要模拟启动。虽然它是一个完整性检查函数 spy 是否按预期工作 - 它确实如此。预期结果是它在 300 毫秒后调用间谍回调。因此,当我 jest.advanceTimersByTime(500)
时,间谍函数应该在 useInterval
回调中至少被调用一次。然而,这并没有发生在测试中。
// Timer.spec.js
describe('Timer', () => {
const spyFunction = jest.fn();
const timer = {
offsetTime: new Date(),
isTicking: true,
elapsedTime: 0
};
let wrapper = null;
beforeEach(() => {
wrapper = mount(<Timer updateTimer={spyFunction} timer={timer} />);
});
afterEach(() => {
wrapper.unmount();
wrapper = null;
});
it('timer should run', () => {
jest.useFakeTimers();
expect(spyFunction).not.toHaveBeenCalled();
wrapper.find('button.start').simulate('click', { button: 0 });
// works as expected
expect(spyFunction).toHaveBeenCalled();
// doesn't seem to work for some reason
jest.advanceTimersByTime(500);
// will fail; function only called once by the button.start. It should have been called at least twice.
expect(spyFunction).toHaveBeenCalledTimes(2);
});
});
我认为问题与 useInterval
挂钩有关。我怀疑在能够调用回调之前发生垃圾收集。有什么方法可以测试 useInterval
钩子的回调调用 updateTimer
又名 Jest.Fn
?
// useInterval hook
import { useEffect, useRef } from 'react';
export const useInterval = (callback, delay) => {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};
问题出在 jest.useFakeTimers();
,更具体地说是在您调用它的地方。在伪造计时器之前,您正在将组件安装在 beforeEach
中。因此,您的 useInterval
挂钩设置为使用真实的 setInterval
,而不是开玩笑的伪造。你在测试中调用 jest.useFakeTimers();
的那一刻为时已晚,因为它不会影响任何东西(你没有创建任何与计时器相关的新东西)。
如果您在 beforeEach
中的 mount
之前移动它,测试将起作用。
尝试做这样的事情
it("should run the count until you get to 00:00", () => {
loadWrapperAndInjectDependencies();
jest.useFakeTimers();
const resendOtpCode = wrapper.find({ testID: "buttonResendCode" });
resendOtpCode.simulate("press");
jest.runAllTimers();
const timeText = wrapper.find({ testID: "resendCodeTime" });
expect(timeText.props().children).toBe("00:00");
});