visit
The issue in this particular case was that there were several additional locations where
setTimeout
and setInterval
had been implemented ... and the complete codebase needs to run before testing. Because all code runs, there are "bleed over" cases where other code is interfering with the tests.
This pattern is a patch using a pattern I am somewhat uncomfortable with as a tester, but given the amount of code already in place, this seemed like a reasonable option.
var timeoutIds = [];
var intervalIds = [];
var windowSetTimeout = window.setTimeout;
var windowSetInterval = window.setInterval;
window.setTimeout = function testSetTimeout() {
var id = windowSetTimeout.apply(this, arguments)
timeoutIds.push(id);
return id;
};
window.setInterval = function testSetInterval() {
var id = windowSetInterval.apply(this, arguments)
intervalIds.push(id);
return id;
};
afterEach(function() {
timeoutIds.forEach(window.clearTimeout);
intervalIds.forEach(window.clearInterval);
timeoutIds = [];
intervalIds = [];
});
Part of the issue I see with these examples is that
setInterval
is not covered.
Given a function with a timeout inside:var testableVariable = false;
function testableCode() {
setTimeout(function() {
testableVariable = true;
}, 10);
}
Use done as a means to tell the test that the
expect
will be checked Asynchronously, allowing enough time to expire for the setTimeout in the code above to run ...
it('expects testableVariable to become true', function(done) {
testableCode();
setTimeout(function() {
expect(testableVariable).toEqual(true);
done();
}, 20);
});
Additionally, the timer behavior could be mocked - this method allows jasmine to step the time forward.
it('expects testableVariable to become true', function() {
jasmine.clock().install();
testableCode();
jasmine.clock().tick(10);
expect(testableVariable).toEqual(true);
jasmine.clock().uninstall();
});
And, we can now use async/await from
it('expects testableVariable to become true', async function() {
await testableCode();
expect(testableVariable).toEqual(true);
});
Also, the original code could be refactored to take the function inside the
setTimeout
out in a way to make it testable.
var testableVariable = false;
function testableAfterTimeout() {
testableVariable = true;
}
function testableCode() {
setTimeout(testableAfterTimeout, 10);
}
With this code, we can simply test the
testableAfterTimeout
function directly.it('expects testableVariable to become true', function() {
testableAfterTimeout();
expect(testableVariable).toEqual(true);
});
var testableVariable2 = false;
function testableCode2(){
var counter = 1;
var interval = setInterval(function (){
if (counter === 5){
testableVariable = true;
clearInterval(interval);
}
counter++;
}, 500);
return interval;
}
Use done as a means to tell the test that the
expect
will be checked Asynchronously, allowing enough time to expire for the setTimeout in the code above to run.it('expects testableVariable2 to become true', function(done) {
testableCode2();
setTimeout(function() {
expect(testableVariable2).toEqual(true);
done();
}, 4000);
});
Additionally, the timer behavior could be mocked - this method allows jasmine to step the time forward.
it('expects testableVariable2 to become true', function() {
jasmine.clock().install();
testableCode2();
jasmine.clock().tick(4000);
expect(testableVariable2).toEqual(true);
jasmine.clock().uninstall();
});
Also, the original code could be refactored to take the function inside the
setInterval
out in a way to make it testable.
var testableVariable2 = false;
var counter = 1;
var interval;
function testableAfterInterval() {
if (counter === 5){
testableVariable = true;
clearInterval(interval);
}
counter++;
}
function testableCode2() {
counter = 1
interval = setInterval(testableAfterInterval, 500);
return interval;
}
With this code, we can simply test the
testableAfterInterval
function directly.it('expects testableVariable2 to become true', function() {
counter = 5;
testableAfterInterval();
expect(testableVariable2).toEqual(true);
});
Also published at