Author Archive

Today we want to share a nice integration between JsTestDriver and SinonJS that allows for great stubbing and mocking facilities in your JavaScript tests.

A common thing needed when writing JavaScript unit tests is to mock out an object or the return value of a function. In addition to this, it’s important to restore the original object or function at the end of the test. Let’s check out a pretty trivial example:

Rally.getVersion = function() {
  return 2;
};

Rally.isSupported = function(operation) {
  return Rally.getVersion() > 1;
};

TestCase('IsSupportedTest', {
  'test should support version greater than one': function() {
    assertTrue(Rally.isSupported('read'));
  },
  'test should not support version less than two': function() {
    var orig = Rally.getVersion = function() { return 1; };
    try {
      assertFalse(Rally.isSupported('read'));
    } finally {
      Rally.getVersion = orig;
    }
  }
});

Now let’s say you have 20 tests around the Rally.isSupported method. Each of which you might need to clean up any mocked out methods. This would get tedious, so you might leverage the setUp and tearDown methods provided by JsTestDriver’s TestCase:

TestCase('IsSupportedTest', {
  setUp: function() {
    this.origGetVersion = Rally.getVersion;
  },
  tearDown: function() {
    Rally.getVersion = this.origGetVersion;
  },

  'test should support version greater than one': function() {
    assertTrue(Rally.isSupported('read'));
  },
  'test should not support version less than two': function() {
    Rally.getVersion = function() { return 1; };
    assertFalse(Rally.isSupported('read'));
  }
});

Another way to do this would be to use the SinonJS framework, specifically the stub() method. This allows you to stub out an existing method on an object, and provide expected return values as well as a variety of methods to assert the method was called:

TestCase('IsSupportedTest', {
  setUp: function() {
    this.getVersion = sinon.stub(Rally, 'getVersion');
  },
  tearDown: function() {
    this.getVersion.restore();
  },

  'test should support version greater than one': function() {
    this.getVersion.returns(2);
    assertTrue(Rally.isSupported('read'));
  },
  'test should not support version less than two': function() {
    this.getVersion.returns(1);
    assertFalse(Rally.isSupported('read'));
  }
});

This is nice and clean. The restore() method will automatically unwrap the stubbed method, and we can easily control the return values of the stubbed method. But we can make this even cleaner. We can have sinon automatically restore all stubbed and mocked methods upon destruction of the TestCase object. This can be done by leveraging Sinon’s sinon.testCase, which is an integration wrapper between SinonJS and JsTestDriver.

(function() {
    var origJstestDriverTestCase = window.TestCase;
    var origSinonTestCase = window.sinon.testCase;

    window.sinon.testCase = function() {
        throw Error('** Do not call sinon.testCase() directly, TestCase() will do this for you! **');
    };

    var globalSetUp = function() {
        // ... global setup stuff goes here
        if(this.originalSetUp) {
            this.originalSetUp();
        }
    };

    var globalTearDown = function() {
        // ... global tear down stuff goes here
        if(this.originalTearDown) {
            this.originalTearDown();
        }
    };

    window.TestCase = function(name, prototype, optType) {
        prototype.originalSetUp = prototype.setUp;
        prototype.setUp = globalSetUp;

        prototype.originalTearDown = prototype.tearDown;
        prototype.tearDown = globalTearDown;

        prototype = origSinonTestCase(prototype);
        return origJstestDriverTestCase(name, prototype, optType);
    };
})();

With this in place, we can now write the following test:

TestCase('IsSupportedTest', {
  setUp: function() {
    this.getVersion = this.stub(Rally, 'getVersion');
  },

  'test should support version greater than one': function() {
    this.getVersion.returns(2);
    assertTrue(Rally.isSupported('read'));
  },
  'test should not support version less than two': function() {
    this.getVersion.returns(1);
    assertFalse(Rally.isSupported('read'));
  }
});

Wrapping the TestCase’s prototype with sinon.testCase exposes all of the SinonJS methods through the TestCase object – so this.stub(), this.mock(), etc will all work. Additionally, SinonJS will automatically unwrap the stubbed/mocked objects at the end of the test. We’ve also put some logic in to ensure the following will throw an error:

TestCase('IsSupportedTest', sinon.testCase({  // <--- don't need to do this, it's done automatically
  setUp: function() {
    this.getVersion = this.stub(Rally, 'getVersion');
  },

  'test should support version greater than one': function() {
    this.getVersion.returns(2);
    assertTrue(Rally.isSupported('read'));
  },
  'test should not support version less than two': function() {
    this.getVersion.returns(1);
    assertFalse(Rally.isSupported('read'));
  }
}));

We started using Ext JS about 3 years ago. Back then, we were new to Ext JS, and tasked with developing the next generation of components to revamp our UI. We were using Ext JS 2, and wanted infinite flexibility and customization in the components we built. This need, along with the desire to change some of the core Ext JS functionality led us to do some ugly things. We used private methods as extension points, straight copy-n-pasted methods to change one line, and introduced a slew of new config options. While the resulting components fit the bill, we were left with a maintenance and upgrade nightmare. Fast forward a few years and the nightmare got worse – Ext JS 3 was out, but we couldn’t separate the bug fixes from the behavior changes from the newly created features and options. So what did we do? We decided to start over (for the most part), with Ext JS 4, a clean slate, and a new approach to writing good maintainable JavaScript code.

We want to share some of our hard-learned lessons with you so hopefully you’ll avoid the same mistakes. In this post, we will discuss an approach to adding additional behavior to Ext JS that follows good Object Oriented (OO) design principles and leads to code that is more maintainable and much easier to upgrade when the next shiny new version of Ext JS is released. So lets get to it.

The Problem
Imagine you want to enhance the Ext.form.field.ComboBox to allow the dropdown list (an Ext.view.BoundList) to automatically size itself to it’s contents width (a feature that Ext JS should add to the ComboBox IMHO). Let’s call this enhancement the auto-sizing-feature. You might start out by creating a new subclass of ComboBox and adding the additional behavior:

Ext.define('Rally.field.ComboBox', {
    extend : 'Ext.form.field.ComboBox',

    defaultOffset : [0,-1],
    defaultAlign : 'tl-bl?',

    onExpand : function() {
        this.callParent(arguments);
        this.getPicker().on('viewready', this._sizeView, this);
    },

    _sizeView : function() {
        // code to customize BoundList dropdown
    },

    // helper methods used by _sizeView()
    _getMaxWidth : function() { },
    _reSizeBoundList : function() { },
    _reAlignBoundList : function() { }
});

While this will work, it sets us up for some potential issues. Imagine another developer opening up the Rally.field.ComboBox a few months down the road to add some additional functionality specified by another-really-important-feature. Lets say they need to do some size and position calculations. They might use one of the pseudo-private helper methods we’ve added to do this. Or they might see the defaultAlign property and decide to use it. So we’d then have something like this:

Ext.define('Rally.field.ComboBox', {
    ...
    defaultAlign : 'tl-bl?',
    ...
    // helper methods used by _sizeView()
    _getMaxWidth : function() {
        // original code to support auto-sizing-feature here
        this._lastCalcMaxWidth = maxWidth;  // added to support another-really-important-feature
        // more of the orignal code
    },
    _replaceWithShimWhileLoading : function(offset) {
        if (offset === this.defaultAlign) {
            // do stuff
        }
    }
});

Now let’s complicate the situation – a few months after another-really-important-feature was added, Ext JS 4.1 comes out and to our surprise, it provides the ability to auto-size the dropdown based on it’s contents width. So we want to remove the auto-sizing-feature, but we still need another-really-important-feature to work. Hmm, gets a little tricky.

A Better Approach
A better approach to prevent this situation is to use one of the great features provided by Ext JS – plugins. Plugins are by no means a silver bullet, but they do provide a more elegant way to encapsulate new functionality and promote better separation in your code.

Let’s see how we could have implemented auto-sizing-feature as a plugin:

Ext.define('Rally.field.plugin.BoundListAutoSizingPlugin', {
    extend : 'Ext.AbstractPlugin',
    alias : 'plugin.boundListAutoSizingPlugin',

    defaultOffset : [0,-1],
    defaultAlign : 'tl-bl?',

    init : function(comboBox) {
        this.comboBox = comboBox;
        this.comboBox.on('expand', this._onExpand, this);
    },

    destroy : function(comboBox) {
        this.comboBox.un('expand', this._onExpand, this);
        if (picker) {
            picker.un('viewready', this._sizeView, this);
        }
    },

    _onExpand : function() {
        var picker = this.comboBox.getPicker();
        if (picker) {
            picker.on('viewready', this._sizeView, this);
        }
    },

    _sizeView : function() {
        // code to customize BoundList dropdown
    },

    // helper methods used by _sizeView()
    _getMaxWidth : function() { },
    _reSizeBoundList : function() { },
    _reAlignBoundList : function() { }
});

Ext.define('Rally.field.ComboBox', {
    extend : 'Ext.form.field.ComboBox',
    plugins : [{
        ptype : 'boundListAutoSizingPlugin'
    }]
});

As you can see, our Rally.field.ComboBox is now much cleaner, and includes the boundListAutoSizingPlugin. The Rally.field.plugin.BoundListAutoSizingPlugin encapsulates all the code needed to auto-size the dropdown. Now, when Ext JS 4.1 comes out and includes this behavior by default, all we need to do is delete the plugin code and remove it from the list of plugins.

This will also make it much more unlikely that code for another-really-important-feature will be intermingled with the implementation of auto-sizing-feature. But what about code duplication? You might argue the reason that _getMaxWidth was leveraged later was because it already implemented the functionality needed. If you find there is common logic, calculations, or code needed by multiple plugins or other collaborating objects, refactor that code into a public method on Rally.field.ComboBox, and let the plugins or other collaborating object go through that method.

Conclusion
As we mentioned, plugins are not the ideal solution to every problem, but it is a very valuable tool as you being to write more Ext JS-based JavaScript. Look for ways to leverage it to help produce clean, well separated objects and interactions.

At Rally, we write a lot of JavaScript. That means we also write a lot of JavaScript tests.  In this post, we’ll talk about some techniques we are using to mock out dependencies in our JavaScript tests.

Some History
We’ve always had a need to mock out dependencies in our JavaScript tests, but the way we’ve accomplished it has varied widely. At first, we used simple objects and variables:

  'test should switch projects' : function() {
    var actualProjectOid = null;
    var projectSwitcher = {
      switchTo : function(oid) {
        actualProjectOid = oid;
      }
    };
    var projectPicker = new Rally.app.ui.ProjectPicker({
      projectSwitcher : projectSwitcher
    });

    var projectOid = 123;
    projectPicker.fireEvent('projectselect', projectOid);

    assertEquals(
            'Should have switched project using the oid provided by event',
            projectOid,
            actualProjectOid);
  }

This approach is simple and it works, but it leads to lots of extra variables and function definitions. It also doesn’t read very well.

Our next attempt was to use a built-in feature of JsTestDriver – expectAsserts.

  'test should switch projects' : function() {
    expectAsserts(1);

    var projectOid = 123;
    var projectSwitcher = {
      switchTo : function(oid) {
        assertEquals(
              'Should have switched project using the oid provided by event',
              projectOid,
              oid);
      }
    };
    var projectPicker = new Rally.app.ui.ProjectPicker({
      projectSwitcher : projectSwitcher
    });

    projectPicker.fireEvent('projectselect', projectOid);
  }

The expectAsserts will ensure the provided number of asserts were called during the execution of the test case. If the expected number of asserts were not called, the test will fail. There biggest thing we didn’t like about this approach is that if the asserts aren’t called, the error message provided isn’t very helpful. We’d really like to get the message provided to the Assert.areEqual if the test fails. It also doesn’t read as nicely as we’d like.

In Comes Sinon.js

Sinon.js written by Christian Johansen is a great mocking framework for JavaScript testing.  It provides the ability to spy, stub, mock, and verify in your tests.  The documentation is great, and there are features for just about any testing situation you might find yourself in.

  'test should switch projects' : function() {
    var projectSwitcher = {
      switchTo : sinon.stub()
    };

    var projectPicker = new Rally.app.ui.ProjectPicker({
      projectSwitcher : projectSwitcher
    });

    var projectOid = 123;
    projectPicker.fireEvent('projectselect', projectOid);

    sinon.assert.calledWith(projectSwitcher.switchTo, projectOid);
  }

There are a few things to note about this approach.  It leads to readable code – the assert reads like a sentence.  It’s the smallest method in terms of lines of code that we’ve shown. We aren’t creating a lot of unnecessary variables to track execution of methods, and we will get meaningful error messages if the test fails.

This post is a continuation on a series of posts regarding our switch from YUI Test to JsTestDriver (jsTD).  In the first post,  Testing Javasciprt Is … I mean should be … Easy, I described some of the motivations for switching to jsTD as well as a few nice techniques for navigating and creating tests.  In this post, I’d like to talk about how we mapped our existing asserts from YUI-land to jsTD.

What We Had

YUI Test uses a TestNG-like syntax for defining the assert statements.  For example:

Assert.areEqual('foo', name, 'Should have been foo');

All assert statements are under the Assert namespace, and follow the (expected, actual, msg) format.

What We Want

JsTD uses a different syntax, more inline with the JUnit syntax:

assertTrue('Should have been foo', 'foo', name);

In this scheme, assert statements are in the global namespace and follow the (msg, expected, actual) format.

Our Challenge

When Rod and I sat down, our challenge was how to convert our existing Assert.assert*() statements into the assert*() statements that jsTD expects.  We have around 85 individual test files, and around 600 test methods.  This amounts to a significant amount of statements to fix during the conversion.

The Solution

Instead of manually converting each assert statement, we decided to create a mapping file to map from one syntax to the other.  This had a few benefits for us:

  1. We didn’t have to touch any of our assert statements
  2. It allowed us to use our preferred TestNG format (values and then an optional message), which is more consistent with our Java unit tests
  3. It keeps the asserts in a top-level namespace of Assert

Thus our mapping file, test-setup.js, looks something like this:


var Assert = {};

(function() {

  var buildTwoArgList = function(actual, msg) {
      var args = [];
      if (msg) {
          args.push(msg);
      }
      args.push(actual);
      return args;
  };

  var buildThreeArgList = function(expected, actual, msg) {
      var args = [];
      if (msg) {
          args.push(msg);
      }
      args.push(expected);
      args.push(actual);

      return args;
  };

  // thanks Crockford
  var isArray = function(actual) {
    return (typeof actual.length === 'number' &&
          !(actual.propertyIsEnumerable('length')) &&
           typeof actual.splice === 'function');
  };

  // JsTestDriver supports the following:
  //
  //   expectAsserts(count)
  //   fail([msg])
  //   assertTrue([msg], actual)
  //   assertFalse([msg], actual)
  //   assertEquals([msg], expected, actual)
  //   assertSame([msg], expected, actual)
  //   assertNotSame([msg], expected, actual)
  //   assertNull([msg], actual)
  //   assertNotNull([msg], actual)

  Assert = {

      expectAsserts : function(count) {
          expectAsserts(count);
      },

      fail : function(msg) {
          fail(msg);
      },

      areEqual : function(expected, actual, msg) {
          assertEquals(buildThreeArgList(expected,
                                         actual, msg));
      },

      areNotEqual : function(expected, actual, msg) {
          try {
              assertEquals(buildThreeArgList(expected,
                                             actual, msg));
              fail(msg || 'Are not equal');
          } catch (e) { }
      },
      ....
      isArray : function(actual, msg) {
          assertTrue(buildTwoArgList(isArray(actual), msg));
      },

      isFalse : function(actual, msg) {
          assertFalse(buildTwoArgList(actual, msg));
      },

      isNotUndefined : function(actual, msg) {
          assertTrue(buildTwoArgList(typeof actual !==
                                     'undefined', msg));
      }
  };

})();

We then include the test-setup.js file in our jsTestDriver.conf config file between the source files and the test files.

With this in place, we were able to use all of our pre-existing test methods without touching any of the assert statements. It also allows us to define commonly used assert methods within the Assert namespace, and make them available for use in any test.

During the past few releases, we have been building out the next version of Rally, relying heavily on the ExtJS framework.  After evaluating all the different frameworks, we settled on Ext, largely due to its UI components and general architecture and speed.  As we have worked more and more with Ext, we have started to find there are a few key concepts that you should master to become proficient in Ext.  Some of the ideas are very subtle, but can have large performance implications.  Other topics are key to understanding how Ext builds up its Components and event system.

We would like to share this knowledge with you.  Here are five things that you should know and become intimatly familiar with when working in an Ext environment.

1. Ext.fly()

The Ext.Element provides a myriad of useful methods and behaviors.  To use them, you must first create an instance to an Ext.Element.  This is typically done via the following:

var el = Ext.get('left_content_div');
el.setOpacity(0.5);

But this comes at a cost.  Specifically, the overhead of creating and wiring up the Ext.Element instance to the specified DOM element.  This overhead, while small, can add up if executed frequently and with lots of different elements.  Take for example this code, which might be used in a drag-and-drop implementation to determine if we are over a suitable drop target:

onNodeEnter : function(target, dd, e, data) {
  if (Ext.get(target).child('.list-box')) {
    return 'x-dd-no-drop';
  }
  return 'x-dd-drop-ok';
}

Imagine this method being called for each element in the DOM the user drags over while searching for the place they wish to drop.  There could be some potential performance implications!  The solution is to not create an Ext.Element for one-liner method calls.  Instead, we use the Ext flyweight element.  This is a special Ext.Element that has already been instantiated, but not permanently wired to any specific DOM element.  Thus, you could speed up the drag-and-drop code by:

onNodeEnter : function(target, dd, e, data) {
  if (Ext.fly(target).child('.list-box')) {
    return 'x-dd-no-drop';
  }
  return 'x-dd-drop-ok';
}

One thing to note is that you cannot save a reference to the Ext flyweight Element.  So if you need to make multiple method calls, you must balance the overhead of creating a real Ext.Element object with the duplicity of multiple calls to Ext.fly:

var el = Ext.get('some_div');
el.setWidth(200);
el.setOpacity(0.4);
el.update('Working');

or

Ext.fly('some_div').setWidth(200);
Ext.fly('some_div').setOpacity(0.4);
Ext.fly('some_div').update('Working');

2. Ext.apply()

The Ext.apply() method is a very handy method for merging the contents of two or more object literals.  It’s used all over the Ext library, and almost every other Javascript library has an equivalent.  Let’s look at an example:

var Lightbox = function(config) {
  Ext.apply(this, config);
};

This example shows a typical way to provide config options to an object upon creation.  Given the code above, we could create a Lightbox via:

var lightbox = new Lightbox({
  width     : 300,
  height    : 400,
  title     : 'Are you sure....',
  closeable : true
});

The lightbox instance would then contain all the properties, as specified in the object literal.  If you look at any Ext component, this is a typical way they manage the application of config properties to the object.

But you can do more:

var Lightbox = function(config) {
  Ext.apply(this, {
    closeable : false
  }, config);
};

In this mode of operation, the second argument can be thought of as default values, which can be overridden by values in the config object.  Or how about this:

var cloneProps = function(props) {
  return Ext.apply({}, props);
};

This is a quick way to create a copy of an object if you are worried about the side-effects of mutations on those properties (note that this is not a deep copy).

3. Ext.util.Observable

The Observable interface is immensely important when it come to Ext objects and the interactions between them.  Browsing through the documents, you will note the the Observable interface is at the top of almost every UI components prototype chain.  It’s there for a good reason – it enables basic event publishing and subscribing.

If you have worked with any Ext UI component, you will undoubtedly be familiar with events:

widget.on('render', this.onWidgetRender, this);

This provides an easy way to hook into the objects life-cycle, and get notified when the objects state is modified or its view changes.  It’s also simple to add events to an existing component and subscribe to them:

widget.addEvents('clicksave', 'clickclose');

But we are not limited to the set of default Ext objects.  We can easily implement the Observable behaviors in our own objects:

var Car = Ext.extend(Ext.util.Observable, {
  constructor : function() {
    this.addEvents('door-ajar');
  },
  init : function() {
    this.on('door-ajar', this.onDoorAjar, this);
  },
  destroy : function() {
    this.un('door-ajar', this.onDoorAjar, this);
  },
  ...
  onDoorAjar : function(door) {
    this.dashboard.illuminate('door-indicator', door);
  },
  slamTrunk : function() {
    if (this.trunkIsOverflowing()) {
      this.fireEvent('door-ajar', 'trunk');
    }
  }
});

A few things to note about the Car object:

  • We use the Ext.extend() method to “inherit” from the Observable object.
  • We define events that this object may fire using the this.addEvents() method.  You can pass in a variable number of arguments to this method, each one will be defined as a valid event.
  • We can define behavior by subscribing to an event through the this.on() method.  In the example above, when the ‘door-ajar’ event is fired, we will trigger the this.onDoorAjar() method, and set the scope of the method invocation to this.
  • Any time we subscribe to an event, we should wash our hands on the way out by cleaning up that registration through a call to this.un().  You will notice that some objects in Ext will use a single method to setup and remove event hooks to ensure each time we on(), we also un():
bindStore : function(store, register) {
  if (store) {
    if (register === true) {
      store.on('load', this.onStoreLoad, this);
      store.on('add', this.onStoreAdd, this);
      store.on('clear', this.onStoreClear, this);
    } else {
      store.un('load', this.onStoreLoad, this);
      store.un('add', this.onStoreAdd, this);
      store.un('clear', this.onStoreClear, this);
    }
  }
}
  • When we fire the event, we can pass an arbitrary number of arguments.  These arguments will be passed along to any methods that have registered for the event.  In this example, you can see we are passing a single argument – the door that is ajar.  This will be passed to each of the event handler methods as the first argument.

4. Function.createDelegate()

Let’s walk through a scenario of mistaken identity that I’m sure any Javascript programmer has encountered at one time or another.  Take this relatively simple example:

init : function() {
  var handleEvent = function(e) {
    this.process(e, this.name);
  };
  this.addEventProcessor(handleEvent);
}

In the example above, we define a method used to process an event by calling the this.process() method.  We then register this method via the this.addEventProcessor() method and wait.  At some later time, the handleEvent() is finally invoked, and the event doesn’t get processed.  We trace it down to the fact that the ‘this’ inside the callback method is not the same ‘this’ as when we defined the method and registered it.  So we bang our head on the desk, and come up with a solution like this:

init : function() {
  var that = this;
  var handleEvent = function(e) {
    that.process(e, that.name);
  };
  this.addEventProcessor(handleEvent);
}

Again, we wait for the handleEvent() method to run, and we find that things work as expected.  The real issue is that we have no control over the execution scope of the handleEvent() method.  When the method is invoked, we have no gaurantee that ‘this’ is actually what we expect.

A better solution is to define the execution scope of the method via the createDelegate() method.  This method is made available by Ext via the Function objects prototype.  Going back to our example, we could rewrite it as follows:

init : function() {
  var handleEvent =
       this.process.createDelegate(this, [this.name], true);
  this.addEventProcessor(handleEvent);
}

Let’s look at what this is doing:

  • Wraps the this.process() method in a function that explicitly defines the execution scope to the current object – ‘this’ – which is specified by the first argument to createDelegate().
  • The second argument is a list of arguments to pass to this.process() when it is invoked.
  • The third parameter can be false, true, or a number.  When it’s true, it specifies the list provided as the second parameter should be appended to the arguments specified by the caller when the method is invoked.  Thus if the handleEvent() method was invoked with handleEvent(e), the process method would be invoked with process(e, this.name).

5. Function.defer()

Not much to this one, but it’s some nice syntactic-sugar that will help turn lines like this:

var that = this;
setTimeout(function() {
  that.setWidth(200);
}, 300);

Into this:

(function() {
  this.setWidth(200);
}).defer(300, this);

And using the third, optional param to defer(), into this:


this.setWidth.defer(300, this, [200]);

If you look under the covers of Ext, you will notice that this is simply Function.createDelegate() in disguise with one additional parameter.  But, given it’s usefullness and simplicity, I had to include it.

So there you go, five things that can put you on the road to becoming and Ext-pert in no time.  If there are other things you find useful, let us know!

As of late, we have been doing a large amount of front-end development in Javascript.  Most of this work is to support some awesome new features in Rally – like the dashboard and grid framework.  We are finally starting to eat our own dog food by consuming the same web services we have provided our customers for years.  As part of this development, we are ensuring that all new Javascript code is unit tested.

Due to our Javascript testing framework selection, it is difficult and (slightly) time consuming to continuously run the unit tests after each change.  Using YUI Test, we have to switch context out of our IDE to a browser to run the test.  This context switch can easily kill any flow you’ve reached while working on a story.   Additionally, we’ve found that Javascript tests seem to have a barrier for entry that is higher than that of Java unit tests.  This barrier can be enough to persuade even the best of us to skirt around creating a new unit test for a small change.

Given these challenges, and as part of our evolving build process, we’ve decided to focus on making Javascript testing easier, faster, and more tightly integrated into the development cycle.  To accomplish this, we’ve decided to switch testing framework.  We’ve also made strides at integrating these tests into the IDE and maven build process.  To help you avoid some of the pitfalls and challenges we’ve encountered, we want to share a few lessons learned.  If you take these and apply them, testing your Javascript will be … I mean should be … easy (or at least a bit easier).

Easy Navigation From Source to Test

This is key.  The more time you spend trying to remember what the name of the test is and where it’s located, the less time you’ll have to actually write meaningful tests.  If you use IntelliJ, I’m sure you find shift-command-T incredibly useful to switch between a the Java class and its unit tests.  But it doesn’t work for Javascript.  No need to worry, AltN8 comes to the rescue.  You can install this IntelliJ plug-in, setup up some file mappings and key-bindings, and you’re in business.  For example, if your Javascript tests follow a convention of appending Test to the name of the source file, you could setup the following rules:

  1. ^(.*?)\.js$    ==>  $1Test.js
  2. ^(.*?)Test.js$   ==>  $1.js

I also like to change the key-mapping from alt-8 to shift-command-J so that it more closely matches the key-binding for Java unit testing.  With this setup, from your source file, throw down a shift-command-J key sequence and viola,  you’re in the corresponding unit test (if there is more than one match, you can choose from a list).

Create Test Templates

Some test frameworks, like YUI, require a fair amount of setup around the actual test code.  In YUI test, you actually create a HTML file, include the relevant YUI sources, and finally define your tests.  You can type this each time you create a new test – if you feel like practicing your typing for a few hours.  You could open up an existing test and copy & paste to create a new test – but it’s almost inevitable this will lead to copy & paste errors and stale setup code.  A final option is to use your IDE’s templating mechanism.  For IntelliJ, you could easily create a live template to make this task much less intensive.  While the YUI test setup is very verbose, let’s look at a template for another testing framework, JsTestDriver:

TestCase('$TEST_CASE_NAME$', {
  test$NAME$ : function() {
    $BODY$
  }$END$
});

Now when you need to create a new test, just create a blank file, command-J, and type the name of your live template – for the above I use TestCase.  You can then tab through the replacement fields and put in your values.  Beautiful.

Similarly, you could create a live template for adding a new test method to a pre-existing TestCase:

  test$NAME$ : function() {
    $BODY$
  }$END$

With this template, you can easily add a new test method from within the TestCases prototype,

Select a Good Testing Framework

There are many good Javascript testing frameworks out there.  It’s up to you to pick the one that makes the most sense for you project.  We have done a lot of testing with the YUI Test framework, but have recently decided to make the move to JsTestDriver.  There are good and bad points to both.  For us, the speed and simplicity of the tests in JsTestDriver made it the clear winner.

In YUI Test, tests are defined in HTML files and run manually from the browser.  We’ve added some niceties around this, such as a JSP runner that would iterate over all HTML test files in a given directory, run the test in an iframe, and report failed tests when they happen.  However, there is a lot of overhead with loading up a separate HTML file for each TestCase or TestSuite.  Additionally, the barrier to entry for creating a new test was large.  In fact, too large.  There was a lot of HTML and Javascript/CSS includes that you needed to setup, instantiate a logging class, and define the tests.  This makes the tests long and difficult to read.

We wanted to remove all the HTML wrapping the tests, and just have the tests.  Removing the actual tests from all the surrounding cruft seemed to make them much more readable and maintainable.  This is exactly what JsTestDriver is setup to do.  In this framework, you run a server that is able to serve out all your source and test files.  You then point at least one browser at the server, and run the tests by issuing a command in the terminal.  Each browser registered with the server loads up the source files, the test files, runs the test cases, and reports the results.  This provides a distributed test environment with the ability to run your tests on a myriad of different browser/OS combos.  It integrates with continuous build systems, and runs wickedly fast.

But, there are some issues:

  1. How do we map between Asserts.*() and assert*()?
  2. How do we override or modify globally namespaced objects?  (the source files are no longer reloaded before each test, thus removing the built-in sandboxing of YUI Test)
  3. How do we build up DOM dependencies needed to run the test?
  4. How can we automate the start server, attach browsers, run tests cycle?
  5. What about asynchronous testing – like YUI Tests’ wait() method?

In some follow-up posts, I’ll talk about each issue in more detail and how we have (or are trying) to solve it.  Stay tuned!

I’ve been working on an interesting defect over the past few days around general slowness and “Slow Script” errors for webkit-based browsers (Safari and Chrome).  This is an issue that has popped up a few times over the last couple months, and we had yet to find the silver bullet.

In our previous attempts, our research (When innerHTML isn’t Fast Enough among others) had pointed us to the fact that setting innerHTML on a node in the DOM can be slow.  As a work-around we adopted a solution like this:

RALLY.setInnerHTML = function(el, html) {
    if (RALLY.isIE || RALLY.isGecko) {
        el.innerHTML = html;
        return;
    }

    var nextSibling = el.nextSibling;
    var parent = el.parentNode;
    parent.removeChild(el);
    el.innerHTML = html;
    if (nextSibling) {
        parent.insertBefore(el, nextSibling);
    } else {
        parent.appendChild(el);
    }
}

As you can see, when running in Safari or Chrome, remove the element from the DOM, then set its innerHTML, and finally put it back in the DOM.  With this fix in place, and by replacing occurrences of  el.innerHTML = someHtml with RALLY.setInnerHTML(el, someHtml), we were able to realize a marginal performance improvement.

Recently, however, we’ve been experiencing an increasing number of cases where Safari and Chrome are too slow, to the point where you see the spinning beach ball of death far too often.  The slowness always seems to be around operations that re-render large parts of the view.  As I started digging in and profiling operations, I was able to pinpoint the slowness to the insertBefore() and appendChild() DOM methods.  Looking at our RALLY.setInnerHTML method above, it’s easy to see where this happens.

But these are browser-implemented DOM methods.  How am I supposed to speed these up?  After banging my head on the desk for awhile, I sat down to rehash the problem with James (@james_estes) and Will (@trrbocharged).  They seemed to believe it had something to do with the content we were putting into the node outside of the DOM that was causing the insert/append to be so slow.  It turns out that in most cases, we were slamming a huge amount of content into the element while outside the DOM.  There was something about either the shape or content of this element that was causing the insert/append to be so slow.

After trying a few different things, we finally narrowed in on the styling and CSS classes.  We removed all the stylesheets, and ran through the benchmark tests again.  Things were amazingly fast. It seems that webkit is rather unbashful in triggering reflows when inserting/appending a node into the DOM that contains a lot of content.  So then we tried this:

RALLY.setInnerHTML = function(el, html) {
    if (RALLY.isIE || RALLY.isGecko) {
        el.innerHTML = html;
        return;
    }

    var nextSibling = el.nextSibling;
    var parent = el.parentNode;
    var display = el.style.display || 'block';

    el.style.display = 'none';
    parent.removeChild(el);
    el.innerHTML = html;
    if (nextSibling) {
        parent.insertBefore(el, nextSibling);
    } else {
        parent.appendChild(el);
    }
    el.style.display = display;
}

it was nearly as fast as the tests without the stylesheets.  For the given benchmark, we were able to reduce the time spent in the insertBefore() or appendChild() methods from 39 seconds down to 3 seconds.  With other data sets, we were able to realize up to a 30x improvement.

I’m continuing to research the root cause of this, but I’m guessing it has to do with how webkit processes the inserted/appended node and all of its children.  My initial throught is that as it builds the DOM and encounters classes and inline styles, it causes multiple reflows.  Perhaps the act of appending the display=none node supresses the need to trigger a reflow while building up the DOM, and the subsequent unsetting of display=none causes one (or a minimal set) of reflows to paint the new elements.