Simple helper method for async testing with Jasmine and RequireJS
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,
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
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.
1 it "should do something nice", -> 2 waitsFor -> 3 isWorkCompleted() 4 5 runs -> 6 completedWork().doSomethingNice() 7
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
1ait = (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 7 8 runs -> 9 arrayOfModules = Array.prototype.slice.call readyModules 10 testFn(arrayOfModules...)
ait(), it is just to keep up with the spirit of jasmine methods like
itfor the test case and
xitfor 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
modulesargument) and then proceeding with the call to the
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.
To make things a little clear, here is an example usage:
1describe 'My obedient Model', -> 2 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!" 7
The test case should do something nice, takes in two modules: obedient_model and sub_model, which resolve to the arguments:
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.