SHARE
Facebook X Pinterest WhatsApp

Using Jasmine 2.0’s New done() Function to Test Asynchronous Processes

Written By
thumbnail
Rob Gravelle
Rob Gravelle
Mar 19, 2014

Using Jasmine 2.0’s New done() Function to Test Asynchronous Processes

Just recently, I wrote about Jasmine 2.0 in my Testing DOM Events Using jQuery and Jasmine 2.0 article. It described how to use spies to verify that a given event was triggered and that it resulted in the expected action. While adequate for simple events such as a message being displayed and the like, spies by themselves aren’t enough to test longer asynchronous processes. For those, you need to use the new done() method. It replaces the runs() and waitsFor() methods of earlier jasmine versions to make it more similar to other testing frameworks. In today’s article, we’re going to learn how to use Jasmine 2.0‘s done() method to test DOM events without polluting the test subject page with test code.

Using the done() Function

The done() function is always passed to the beforeEach(), afterEach(), and it() test methods as an argument, whether you need it or not. To use it, include the done argument to the method and the call it after all of the processing is complete. This will usually be in the success callback function of Ajax calls and the pertinent event listener of DOM events. To illustrate, here is a unit test for menu retrieval. The first beforeEach() does not include the done function because there is no asynchronous processing taking place within it. However, the beforeEach() of the nested describe (“when retrieved by name”) does because the menus’ getMenuByName() function is asynchronous. The done() call is made within the success() callback function to instruct jasmine that beforeEach() has terminated and it is now safe to continue with the it() function. There are no asynchronous events in the it() function, so the done() function is not utilized, although we could include it if we needed to.

describe("Menu Tests", function() {
  var menus;
  
  beforeEach(function() { 
    menus = new Menus();
    menus.add(new Menu({
        name:   'dinner',
        season: 'Fall'
    }));
  });
  
  describe("when retrieved by name", function() { 
    var menu;
    
    beforeEach(function(done) {
      menu = menus.getMenuByName('dinner', {
        success: function () {
          done();
        }
      });
    });
  
    it("should have a valid season", function() { 
      expect(menu.season).toEqual("Fall");
    });
  });
});

The Test Page

The following jQuery-enabled web page contains two DIVs: one to display messages and one that shows restaurant menus. On page load, it fetches new menus from the server and includes a link to each using the restaurant name as the item name. Our tests will verify that the appending of the new menus works as expected.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>Target Page</title>
		<style>
			#results {
				font-size: 1.2 rem;
				margin: 10px;
				height: 50px;
			}

			#content-area {
				background-color: #FFFFFF;
				height: 100px;
				padding-left: 5px;
				width: 200px;
			}
		</style>
		<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
		<script type="text/javascript" src="scripts/menus.js"></script>
	</head>
	<body>
		<div id="results"></div>
		<div id="content-area"></div>
	</body>
</html>

In the menus.js file, the getNewMenus() function is invoked on the page’s onload event. Inside the event handler, an Ajax get() call retrieves the restaurant data from the server. Presumably, a server-side process would handle the data retrieval, but for the sake of simplicity, this call simply reads a text file and appends the HTML contents into the content-area DIV via the successCallback() method. Our unit test will cover the end-to-end process from the page load to the new restaurant being inserted into the DOM. Since there is no way of knowing how long that will take, it is an ideal candidate for the done() function.

function successCallback(data) { 
  $( "#content-area" ).append( data );  //if this doesn't work try data.children[0]
}

function failCallback(data) { 
  $( "#results" ).text( "Load new menus failed." );  
}

function getNewMenus() {
  //formatted like <p><a href="/restaurant/Robs_Bistro/#title">Robs Bistro</a></p>
  $.get( "new menus.txt", successCallback).fail(failCallback);
}

$(document).ready(function($) {
  getNewMenus();
});

The SpecRunner HTML page

Jasmine unit tests run in a page called the Spec Runner. It references the required jasmine, jQuery, and jasmine-jquery libraries, as well as the menus.js script file, which we’ll be testing.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DOM Tests using Jasmine v2.0</title>
  <link rel="shortcut icon" type="image/png" href="images/jasmine_favicon.png">
  <link rel="stylesheet" type="text/css" href="scripts/jasmine-master/lib/jasmine-core/jasmine.css">

  <script type="text/javascript" src="scripts/jasmine-master/lib/jasmine-core/jasmine.js"></script>
  <script type="text/javascript" src="scripts/jasmine-master/lib/jasmine-core/jasmine-html.js"></script>
  <script type="text/javascript" src="scripts/jasmine-master/lib/jasmine-core/boot.js"></script>
  <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
  <script type="text/javascript" src="scripts/jasmine-master/lib/jasmine-core/jasmine-jquery.js"></script>
  
  <script type="text/javascript" src="scripts/menus.js"></script>
  <script type="text/javascript">
    //test code goes here
  </script>
</head>
<body>
</body>

The Test Code

We’ll need access to the DOM because we have to check that the new menu link was appended to the contents-area DIV. An easy way to access the DOM without altering the original page is to use the jasmine-jquery library’s setFixtures() method. It accepts an HTML string but treats it as a proper DOM object allowing us to run tests against it. In fact, you can append the whole test page in there without performing any extra parsing. Getting the HTML code to insert can be accomplished using Ajax. In the success callback, we can pass the data directly to setFixtures().

Due to the numerous Ajax calls, the it() function won’t know when to begin, which will result in a timeout. Therefore, we need to insert a call to done() at the end of the getNewMenus() Ajax success callback function because it’s the most likely place in the chain of events that the DOM has been updated. JavaScript is versatile enough that you could alter the original function on the fly and include the done() call on the last line. However, there’s an easier way. If you’re familiar with Aspect Oriented Programming (AOP), you are no doubt aware that it is possible to wrap a proxy object around the original function so that it includes the extra code. The wrapper basically contains a call to the original function, followed by the done() invocation.

  //test code
  describe("Test GetNewMenus()", function() {
  	beforeEach(function(done) {	 
  		$.ajax({
		   url: 'targetPage3.html',
		   data: {},
		   success: function (data) {
				  setFixtures(data);
		  		successCallback = (function(originalFunction) {
    	      return function(data) {
        			originalFunction(data);
        			done();
        		};
      	 })(successCallback);
		   },
		   dataType: 'html'
		  });
   });
 });

The it() function contains several expectation matchers form the excellent jasmine-jquery library. We used it in the last article as well. Using it, we can verify that the listEntry exists, is in the DOM, and contains an <a> element that includes the restaurant name and the expected URL.

   
it ("should fetch the new restaurant meta data from the server.", function() {
    var listEntry = $("#content-area > p").last();
    expect(listEntry).toExist();
    expect(listEntry).toContainElement('a');
    expect(listEntry).toContainText("Robs Bistro");
    expect(listEntry.children('a:first')).toHaveAttr('href', '/restaurant/Robs_Bistro/#title');
});

Here is a screenshot that shows the fixtures as they appear in Firefox.

The fixtures are replaced by the results after a successful test run.

Conclusion

Jasmine is one of the most successful JavaScript testing libraries out there, and for good reason. It’s equally well suited for GUI testing as well as background business processing. There were some rumblings about the use of the runs() and waitsFor() functions, but with the release of 2.0, those complaints should be put to rest.

Recommended for you...

The Revolutionary ES6 Rest and Spread Operators
Rob Gravelle
Aug 23, 2022
Ahead of Time (AOT) Compilation in Angular
Tariq Siddiqui
Aug 16, 2022
Converting a JavaScript Object to a String
Rob Gravelle
Aug 14, 2022
Understanding Primitive Type Coercion in JavaScript
Rob Gravelle
Jul 28, 2022
HTML Goodies Logo

The original home of HTML tutorials. HTMLGoodies is a website dedicated to publishing tutorials that cover every aspect of being a web developer. We cover programming and web development tutorials on languages and technologies such as HTML, JavaScript, and CSS. In addition, our articles cover web frameworks like Angular and React.JS, as well as popular Content Management Systems (CMS) that include WordPress, Drupal, and Joomla. Website development platforms like Shopify, Squarespace, and Wix are also featured. Topics related to solid web design and Internet Marketing also find a home on HTMLGoodies, as we discuss UX/UI Design, Search Engine Optimization (SEO), and web dev best practices.

Property of TechnologyAdvice. © 2025 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.