Sunday, February 12, 2012

Headless JavaScript testing, Sinon.js, Fake Timers and Rhino

Recently I've started to use TDD approach with development of client-side code/JavaScript. The code on front-end become more complex and it have to be tested along with back-end code.

One of first things which I've found was Sinon.js - the mock library to make my life easier. Btw, I'm using Jasmine for writing tests. So, everything looks great until I've tried to integrate my tests into our CIA (Jenkins).

The tests which working great within browser failed using rhino/env.js. That was weird and unfortunately tracebacks were useless. I've started to go deeper and found an article which make things clear to me: Sinon.js have issues with timers.

Update: I've fixed issue with Sinon.JS Fake Timers and Rhino.js

I've made small stub to replace functionality of fake timeouts from Sinon.js and wow, tests are passed!


// setTimeout/clearTimeout stub using Underscore.js
var FakeTimeout = function () {
  var self = this,
  timers = [],
  counter = 1,
  timeoutOrig = setTimeout,
  clearOrig = clearTimeout;

  // add new timeout to the queue
  this.setTimeout = function (f, timeout) {
    var id = counter++;
    timers.push({
      'callback': f,
      'timeout': timeout,
      'id': id
    });
    return id;
  };

  // cleanup timeout
  this.clearTimeout = function (id) {
    timers = _.filter(timers, function (item) {
      return item.id !== id;
    });
  };

  // tick the clock by timeout
  this.tick = function (timeout) {
    var timerToRemove = [];
    _.each(timers, function (timer) {
      if (timer.timeout - timeout <= 0) {
        timer.callback();
        timerToRemove.push(timer);
      } else {
        timer.timeout = timer.timeout - timeout;
      }
    });
    timers = _.difference(timers, timerToRemove);
  };

  // install fake timers
  this.install = function () {
    setTimeout = _.bind(this.setTimeout, this);
    clearTimeout = _.bind(this.clearTimeout, this);
  };

  // restore the original timers
  this.restore = function () {
    setTimeout = timeoutOrig;
    clearTimeout = clearOrig;
  };
};

Here is how to use it:
// Usage:
var timeout = new FakeTimeout();

// Somewhere in your code
timeout.install();

// ... some functionality which use setTimeout/clearTimeout
timeout.tick(1000);

// restore the state of setTimeout/clearTimeout
timeout.restore()


The code require Underscore.js 

The conclusion is to keep it simple. I don't get why you guys need so much code for such simple thing.

3 comments:

  1. Well, line count comparison is kinda misleading here. Your code does not handle setInterval, and Sinon also includes code to sync the Date constructor with the clock.

    That's why we need "so much code for such a simple thing".

    Anyway, if you had taken the time to report your issues back I would have been glad to help you figure them out.

    ReplyDelete
    Replies
    1. Thanks for clarification. I definitely will report an issue.

      Also I've read the code more carefully and now got an idea about Date.

      Delete
  2. I don't think there is any program you could write that could detect whether all trues are passed as a reference to an immutable object that resides in a single memory location (as in Rhino) and one where it is a tagged union that is copied as in most other interpreters.
    I ask so because I can literally do all this in 1 line of python. I am sure there's a more elegant way to write this in underscore or javascript.

    Javascript Ebooks

    ReplyDelete