Simple helper method for async testing with Jasmine and RequireJS

Unit testing in Javascript, especially with RequireJS can be a bit of challenge. Jasmine, which is our unit testing framework does not have any out of the box support for RequireJS. I have seen a few ways of integrating RequireJS but that requires hacking the SpecRunner.html file, the main test harness that executes all jasmine tests. That wasn’t really an option for us as we were using a ruby gem called jasmine to auto generate this html file from our spec files. There is however an experimental gem created by Brendan Jerwin that provides RequireJS integration. We did consider that option before ruling it out for lack of official support. After a bit of flailing around, we finally hit upon a little nugget in the core jasmine framework that seemed to provide a solution.

Async tests in Jasmine

For a long time, most of our tests used the standard prescribed procedure in jasmine, which is describe() with a bunch of it()s. This worked well for the most part until we switched to RequireJS as our script loader. Then there was only blood red on our test pages.

Clearly jasmine and RequireJS have no mutual contract, but there is a way to run async tests in jasmine with methods like runs(), waits() and waitsFor(). Out of these, runs() and waitsFor() were the real nuggets, which complement each other when running async tests.

waitsFor() takes in a function that should return a boolean when the work item has completed. Jasmine will keep calling this function until it returns true, with a default timeout of 5 seconds. If the worker function doesn’t complete by that time, the test will be marked as a failure. You can change the error message and the timeout period by passing in additional arguments to waitsFor().

runs() takes in a function that is called whenever it is ready. If a runs() is preceded by a waitsFor(), it will execute only when the waitsFor() has completed. This is great since it is exactly what we need to make our RequireJS based tests to run correctly. In code, the usage of waitsFor() and runs() looks as shown below. Note that I am using CoffeeScript here for easier readability.

--- Short CoffeeScript Primer ---
In CoffeeScript, the -> (arrow operator) translates to a function(){} block. Functions can be invoked without the parenthesis,eg: foo args is similar to foo(args). The last statement of a function is considered as the return value. Thus, () -> 100 would become function(){ return 100; } "With this primer, you should be able to follow the code snippet below."

1     it "should do something nice", ->
2         waitsFor ->
3 			isWorkCompleted()
5         runs ->
6             completedWork().doSomethingNice()

Jasmine meets RequireJS

waitsFor() along with runs() holds the key to running our RequireJS based tests. Within waitsFor() we wait for the RequireJS modules to load and return true whenever those modules are available. In runs() we take those modules and execute our test code. Since this pattern of writing tests was becoming so common, I decided to capture that into a helper method, called ait().

 1 ait = (description, modules, testFn)->
 2     it description, ->
 3         readyModules = []
 4         waitsFor ->
 5             require modules, -> readyModules = arguments
 6             readyModules.length is modules.length # return true only if all modules are ready
 8         runs ->
 9             arrayOfModules = readyModules
10             testFn(arrayOfModules...)
If are wondering why the name ait(), it is just to keep up with the spirit of jasmine methods like it for the test case and xit for ignored test case. Hence ait, which stands for “async it”. This method takes care of waiting for the RequireJS modules to load (which are passed in the modules argument) and then proceeding with the call to the testFn in runs(), which has the real test code. The testFn takes the modules as individual arguments. Note the special CoffeeScript syntax arrayOfModules... for the expansion of an array into individual elements.

The ait method really reads as: it waitsFor() the RequireJS modules to load and then runs() the test code

To make things a little clear, here is an example usage:

1 describe 'My obedient Model', ->
3     ait 'should do something nice', ['obedient_model', 'sub_model'], (ObedientModel, SubModel)->
4         subModel = new SubModel
5         model = new ObedientModel(subModel)
6         expect(model.doSomethingNice()).toEqual "Just did something really nice!"

The test case should do something nice, takes in two modules: obedient_model and sub_model, which resolve to the arguments: ObedientModel and SubModel, and then executes the test code. Note that I am relying on the default timeout for the waitsFor() method. So far this works great, but that may change as we build up more tests.