Feature: clear all Timers when doing restore
See original GitHub issueWe understand you have a problem and are in a hurry, but please provide us with some info to make it much more likely for your issue to be understood, worked on and resolved quickly.
- FakeTimers version : please verify that the bug exists in the latest FakeTimers release 9.0.11
- Environment : node 16.14.2
- Example URL :
- Other libraries you are using: mocha for showing the behavior.
What did you expect to happen?
When I start to useFakeTimers, all global Timer functions in Node will be replaced by Sinon, including the clear methods. It means if the Timers are set up after fakeTimer is activated, and Sinon is restored before all pending Timers are cleared, it will be not possible to clear the timers afterward.
I’d expect there’s an API to opt-in clearing all pending Timers that are set after fakeTimer is activated on doing sandbox.restore(), it will bring more predictivity to the test behavior, especially when those Timers are set by the libs where the TimerIDs are not trackable.
To add to it, this issue can happen a lot when testing codes that work with RxJS, which heavily relies on clearInterval to work well to clean up the ongoing tasks, otherwise an explicit error will be thrown in NodeJS process (see RxJS code)
What actually happens
as described above.
How to reproduce
Describe with code how to reproduce the faulty behaviour or link to code on JSBin or similar
Really long code sample or stacktrace
// I’m using mocha here
it('fakeTimer', async function () {
const clock = sandbox.useFakeTimers();
const spy = sandbox.spy();
const intervalId = setInterval(spy, 100);
console.log('about to tick 1s');
await clock.tickAsync(1000);
sandbox.assert.callCount(spy, 10);
console.log('about to tick 1s');
await clock.tickAsync(1000);
sandbox.assert.callCount(spy, 20);
sandbox.resetHistory();
sandbox.restore();
clearInterval(intervalId); // this will simply not work, because clearInterval is different from the one Sinon is using, and it's not possible clear the Timers Sinon has set. In this example we can move this line before sandbox.restore(); to solve, but if we can't get the intervalId then we can't clear it.
console.log('about to tick 1s (after clearInterval)');
await clock.tickAsync(1000);
sandbox.assert.callCount(spy, 0);
});
// I had this as a workaround
it('fakeTimer', async function () {
const clock = sandbox.useFakeTimers();
const spy = sandbox.spy();
const setIntervalSpy = sandbox.spy(global, 'setInterval');
const intervalId = setInterval(spy, 100);
console.log('about to tick 1s');
await clock.tickAsync(1000);
sandbox.assert.callCount(spy, 10);
console.log('about to tick 1s');
await clock.tickAsync(1000);
sandbox.assert.callCount(spy, 20);
setIntervalSpy.returnValues.map((intervalTimerId) => global.clearInterval(intervalTimerId));
sandbox.resetHistory(); // this has to be before restore...
sandbox.restore();
console.log('about to tick 1s (after clearInterval)');
await clock.tickAsync(1000);
sandbox.assert.callCount(spy, 0);
});
Issue Analytics
- State:
- Created a year ago
- Comments:7 (5 by maintainers)
Yes, that should be right.
Very smart, I took the tip of Sinon’s original author Christian Johansen in his TDD book on JS and created a repo for learning tests years ago. Very nice for solidifying understanding.
https://runkit.com/xiaosha/628d2d19e3e2060008656cd5 I try to make 2 tests, one with original sinon, the second clearable sinon. Both do the same steps, and assert same expectation, while the first will fail but second pass. The only difference is ClearableSinon does some more thing during restore.