Testing setTimeout & setInterval With Jasmine-Node
Jasmine Node
Jasmine has a very complicated way of testing setTimeout
and setInterval
that I, frankly, don't understand very well. To add to the complication, my code is server-side, so I can't just spyOn( window, 'setInterval' )
. Jasmine also gives a poor example with trivial code--not code someone (like myself) would actually be testing if they actually needed to refer to the docs.
My Function
var update = require( './update' ).update;
exports.updateEveryMinute = function() {
// Check every minute
return setInterval( update, 1000 * 60 );
};
This needs testing. Node.js docs explain really well that setInterval
is not on the window, it's on a globals
object:
In browsers, the top-level scope is the global scope. That means that in browsers if you're in the global scope var something will define a global variable. In Node.js this is different. The top-level scope is not the global scope; var something inside an Node.js module will be local to that module.
Test Me
tl;dr:
// require the function you're testing
var updateEveryMinute = require( './intervals' ).updateEveryMinute;
// require the function called by setInterval.
var update = require( './update' ).update
describe('updateEveryMinute', function() {
var intervalId;
afterEach(function() {
clearInterval( intervalId );
});
it('should be defined as a function', function() {
expect( typeof updateEveryMinute ).toBe( 'function' );
});
it('should set an interval to run update every minute', function() {
spyOn( global, 'setInterval' ).andCallThrough();
intervalId = updateEveryMinute();
expect( global.setInterval ).toHaveBeenCalled();
expect( global.setInterval ).toHaveBeenCalledWith( update, 60000 );
});
it('should return an interval id', function() {
spyOn( global, 'setInterval' ).andCallThrough();
intervalId = updateEveryMinute();
expect( intervalId ).toBeDefined();
expect( typeof intervalId ).toBe( 'object' );
});
});
The Breakdown
// see this in each test
intervalId = updateEveryMinute();
afterEach(function() {
clearInterval( intervalId );
});
setInterval
and setTimeout
return information when created so you can clear them later. After each invocation of the tested function, I need to clear the interval so it doesn't actually call the update
function.
spyOn( global, 'setInterval' ).andCallThrough();
Since there is no window
in Node.js, we'll create a spy on the global
object. And, sure, let's call the real thing using andCallThrough()
; no need to mock here!
expect( global.setInterval ).toHaveBeenCalled();
expect( global.setInterval ).toHaveBeenCalledWith( update , 60000 );
We expect setInterval
to have been called, because that's basically all our function does.
See that second test, though? It's jucy!
I required the same function (update
) in the test spec that I pass to the real function, so I can make a very real comparison without having to replicate all of the code for update
. This won't save you time if you've passed setInterval
an anonymous function--you'll just have to retype it.
intervalId = updateEveryMinute();
expect( intervalId ).toBeDefined();
expect( typeof intervalId ).toBe( 'object' );
One more little thing. In the window
, setInterval
and setTimeout
return numeric ids. However, in Node.js:
Returns an opaque value that represents the timer.
Huh? Excuse me? I didn't catch that. Let's actually go to the timers
module for a better explanation.
Returns a intervalObject for possible use with clearInterval().
Ok, so it's actually an object. Now, we're able to test the function's return value.
Summary
- Ditch Jasmine's mock
setTimeout
andsetInterval
. - Differences between
window
and Node.js:setTimeout
andsetInterval
are on theglobal
object.- They return objects instead of just ids for clearing.
- Don't forget to clear the interval using the id object after each test.