Using the done() Method in Your Jasmine-driven Asynchronous JavaScript Tests

By Rob Gravelle

Using the done() Method in Your Jasmine-driven Asynchronous JavaScript Tests

6/19/13

Jasmine.Async is an add-on library for Jasmine that provides additional functionality to do asynchronous testing. Modeled after Mocha's async test support, it brings the done() function to the Jasmine unit testing environment. In today's article, we'll learn how the Jasmine.Async library makes your asynchronous process tests easier to write.

A Little History

Derick Bailey, the Jasmine.Async author, is a big fan of Jasmine, but noticed that making Jasmine work with asynchronous JavaScript was less than ideal. In particular, he didn't like how runs() and waitsFor() calls were repeated every time you needed to wait for an asynchronous process to complete.

Thus, the Jasmine.Async library was born by borrowing Mocha's use of the done() when the asynchronous code has completed.

How It Works

In my Test Asynchronous Methods Using the Jasmine runs() and waitFor() Methods article, I discussed the Jasmine runs() and waitFor() methods, which were created especially for the purpose of the practical implementation of asynchronous processes such as Ajax. Nonetheless, the runs() and waitFor() methods are equally adept at testing simulated asynchronous calls using the setTimeout(),setInterval(), or various event handlers.

Case in point, the following test employs setTimeout() to create an asynchronous event that takes 500 milliseconds to complete. An initial runs() call sets the default variables and makes the call to setTimeout(). Then the waitsFor() method causes intermittently returns the value of a flag to the Jasmine framework so that it knows when to continue execution of the test. Once the flag has been set to true, the second runs() method verifies our expectations:

describe("async tests", function(){
 var flag, value;
 it("should simulate an asynchronous call", function () { 
  runs(function() { 
    flag  = false; 
    value = 0; 
  
    setTimeout(function() { 
      value++; 
      flag = true; 
    }, 500); 
  });  
  
  waitsFor(function() { 
    return flag; 
  }, "The Value should be incremented", 5000);  

  runs(function() {  
    expect(flag).toEqual(true);   
    expect(value).toEqual(1); 
  }); 
 }); 
}); 

Contrast the above test to the equivalent one below, using the Jasmine.Async library:

describe("async tests", function(){   
  var flag,     
      value, 
      async = new AsyncSpec(this);   
  
  async.beforeEach(function(done){    
    flag  = false;
    value = 0;
    // simulate async stuff and wait 500ms    
    setTimeout(function(){      
      flag = true;  
      value++;    
      done();    
    }, 500);    
  });   
  
  async.it("should simulate an asynchronous call", function(){      
    expect(flag).toBe(true);  
    expect(value).toEqual(1);
  }); 
});

The primary feature of the Jasmine.Async test is the call to the done() function that tells Jasmine that the process has completed. This approach negates the need for the waitsFor() method. In Mocha, if the process hasn't come back done or failed an assertion in a certain amount of time (the default is 2000ms), the test fails. Jasmine.Async relies on Jasmine's timeout of 5 seconds, as the following error confirms:

timeout: timed out after 5000 msec waiting for something to happen

Multiple Asynchronous Operations

You can include as many asynchronous operations as you wish, so long as you always call the done() method afterwards. The assertions won't run until all of the processes have come back or timed out:

function  doSomething() {
  console.log('doSomething()...');
}

function  doSomethingElse() {
  console.log('doSomethingElse()...');
}
    
describe("an async spec", function(){

  // set up the async spec
  var async = new AsyncSpec(this);

  // run an async setup
  async.beforeEach(function(done){
    console.log('beforeEach()...');
    doSomething();

    // simulate async stuff and wait 1 sec
    setTimeout(function(){
      // more code here
      doSomethingElse();
      // when the async process is done, call done()
      done();
    }, 1000); 
  });

  // run an async cleanup
  async.afterEach(function(done){
    // simulate async cleanup
    setTimeout(function(){
      done();
      console.log('afterEach()');
    }, 1000);
  });

  // run an async expectation
  async.it("did stuff", function(done){
    // simulate additional async code
    setTimeout(function(){
      expect(1).toBe(1);
      // all async calls completed
      done();
      console.log( 'done.' );
    }, 1000);    
  });
});

Outputting some text to the console helps verify that each timeout() call is taking about one second each. Notice that even the assertion has a timeout!

Let's take things a step further by introducing an additional expectation. That will cause the beforeEach() and afterEach() to fire twice - once for each expectation - as well as perform an extra increment operation on the value variable. Here's the code:

describe("spec for multiple async calls", function(){
  // set up the async spec
  var value = 0,
      async = new AsyncSpec(this);

  // run an async setup
  async.beforeEach(function(done){
    setTimeout(function(){
      console.log( 'beforeEach()...');
      value++;
      done();
    }, 1000);
  });

  // run an async cleanup
  async.afterEach(function(done){
    // simulate async cleanup
    setTimeout(function(){
      done();
      console.log( 'afterEach()');
    }, 1000);
  });

  async.it("expectations for async call one", function(done){
    setTimeout(function(){
      console.log( 'checking expectation.' );  
      expect(value).toEqual(1);
      done();
      console.log( 'done.' );
    }, 1000);  
  });
  
  async.it("expectations for async call two", function(done){
    setTimeout(function(){
      console.log( 'checking expectation.' );  
      expect(value).toEqual(2);
      done();
      console.log( 'done again.' );
    }, 1000);    
  });
});

Thanks to the console.log() calls, we can see exactly what occurred during the test run:

Conclusion

The Jasmine.Async library adds the goodness of Mocha's done() method to your asynchronous process tests. Use the jasmine.async.js file in development and jasmine.async.min.js in your production environment. The latter is a mere 1 KB! Just drop the appropriate script file in the same folder as your Jasmine scripts and link to it in your Test Runner HTML page.


If you enjoyed this article, please contribute to Rob's rock star aspirations by purchasing one of Rob's cover or original songs from iTunes.com for only 0.99 cents each.

Rob Gravelle resides in Ottawa, Canada, and is the founder of GravelleWebDesign.com. Rob has built systems for Intelligence-related organizations such as Canada Border Services, CSIS as well as for numerous commercial businesses. EmailRob to receive a free estimate on your software project.

In his spare time, Rob has become an accomplished guitar player, and has released several CDs. His former band, Ivory Knight, was rated as one Canada's top hard rock and metal groups by Brave Words magazine (issue #92).

Rob uses and recommends MochaHost, which provides Web Hosting at $3.10 per month, 2 LifeTime Free Domains, and 6 Months Free!



Make a Comment

Loading Comments...

  • Web Development Newsletter Signup

    Invalid email
    You have successfuly registered to our newsletter.
  •  
  •  
  •  
Thanks for your registration, follow us on our social networks to keep up-to-date