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 and setInterval.
  • Differences between window and Node.js:
    • setTimeout and setInterval are on the global object.
    • They return objects instead of just ids for clearing.
  • Don’t forget to clear the interval using the id object after each test.