Skip to content

Instantly share code, notes, and snippets.

@weshouman
Last active January 5, 2024 15:36
Show Gist options
  • Select an option

  • Save weshouman/98fbc490d729c946a9de63ca47aa966f to your computer and use it in GitHub Desktop.

Select an option

Save weshouman/98fbc490d729c946a9de63ca47aa966f to your computer and use it in GitHub Desktop.
Sinon structured fake clock

This snippet is a demo for time-dependent testing of async subfunctions while considering the expected complexity of delays and nesting and the desire for having fine-control for the resolution of each stub.

Note:

  • It's possible to avoid the conditional waiting by using await Promise.resolve(), however that would come at the cost of granual control
  • await Promise.resolve() could be needed to be executed a couple of time, based on the number of promises we want to resolve.
import sinon from 'sinon';
export const timerUtils = {
currentClock: null,
elapsedFakeTime: 0,
useFakeTimer: function() {
console.log('Starting fake timer');
this.currentClock = sinon.useFakeTimers();
this.elapsedFakeTime = 0;
},
pauseFakeTimer: function() {
if (this.currentClock) {
this.elapsedFakeTime = this.currentClock.now;
console.log('Pausing fake timer at:', this.elapsedFakeTime);
this.currentClock.restore();
}
},
resumeFakeTimer: function() {
console.log('Resuming fake timer from:', this.elapsedFakeTime);
this.currentClock = sinon.useFakeTimers({ now: this.elapsedFakeTime });
},
restoreRealTimer: function() {
if (this.currentClock) {
console.log('Restoring real timer');
this.currentClock.restore();
this.currentClock = null;
}
}
};
async function subFunction(index) {
console.log(`UUT: Starting subfunction ${index} at ${new Date().toISOString()}`);
// Simulate an asynchronous operation
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log(`UUT: Completed subfunction ${index} at ${new Date().toISOString()}`);
}
async function mainFunction() {
for (let i = 1; i <= 5; i++) {
// await subFunction(i); // this won't work
await UUT.subFunction(i);
}
console.log(`UUT: Waiting a couple of seconds after subfunctions at ${new Date().toISOString()}`);
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for 2 seconds
console.log(`UUT: mainFunction completed at ${new Date().toISOString()}`);
}
export const UUT = {
mainFunction,
subFunction
};
// UUT.test.js
import { timerUtils } from './timerUtils.js';
import { UUT } from './UUT.js';
import sinon from 'sinon';
import { expect } from 'chai';
const promiseResolvers = [];
describe('Main Function Test', function() {
beforeEach(function() {
timerUtils.useFakeTimer();
console.log(UUT.subFunction)
sinon.stub(UUT, 'subFunction').callsFake((index) => {
console.log(`Stub: subFunction ${index} called, ${new Date().toISOString()}`);
return new Promise((resolve) => {
promiseResolvers.push(resolve);
});
});
console.log(UUT.subFunction)
});
afterEach(function() {
timerUtils.restoreRealTimer();
promiseResolvers.length = 0;
UUT.subFunction.restore();
});
it('should complete mainFunction with controlled promise resolution', async function() {
const mainFunctionPromise = UUT.mainFunction();
let clock;
// Ensure we advance time and resolve promises only after they are pushed
for (let i = 1; i <= 5; i++) {
console.log(`Test: Advancing fake timer for subfunction ${i}`);
timerUtils.currentClock.tick(1000); // Advance time for each subfunction
timerUtils.pauseFakeTimer();
await new Promise(resolve => setTimeout(resolve, 50));
// This does not resume the timer
timerUtils.resumeFakeTimer();
// This resumes the timer
// clock = sinon.useFakeTimers();
console.log(`Test: Resolving subfunction ${i}`);
console.log(`Resolvers count ${promiseResolvers.length}, resolving at index: ${i-1}`)
if (typeof promiseResolvers[i - 1] === 'function') {
promiseResolvers[i - 1](); // Resolve the i-th subfunction's promise
console.log("resolved")
} else {
throw new Error(`Resolver for subfunction ${i} is not a function`);
}
}
console.log('Test: All subfunctions resolved, advancing time for the final wait');
timerUtils.currentClock.tick(2000); // Advance time for the final 2-second wait
await mainFunctionPromise;
console.log('Test: mainFunction should be completed now');
expect(UUT.subFunction.callCount).to.equal(5);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment