Testing JavaScript Code with Jasmine

August 9, 2011

JavaScript has remained a popular, if often derided, language since its very inception back in 1995. In recent years its adoption has skyrocketed as demand for standards- and cross device-compliant Web applications has soared. Such applications tend to be pretty complex projects, and appropriately developers have sought to employ sound testing approaches which ensure the code is operating as expected. While manual testing has its place, automated testing strategies can help to ensure maximum and prolonged coverage of code throughout the project lifetime.

Server-side developers have long experienced the benefits of automated testing solutions, taking advantage of testing frameworks such as RSpec for Ruby, PHPUnit for PHP, and JUnit for Java. If you fall into this crowd but are starting to spend more time with JavaScript, chances are you're fretting over how to apply similar techniques on the client (or or server!) side. Not to worry, as several interesting JavaScript testing frameworks exist, perhaps chief among them Jasmine, a popular open source behavior-driven development framework.

Installing Jasmine

Unlike many of the JavaScript testing frameworks which preceded it, Jasmine is dependency-free, allowing you to run Jasmine without further configuration in any environment which runs JavaScript. While you're able to install Jasmine by downloading it from the project page (or more conveniently by cloning it), I suggest installing it as a Ruby gem as the gem offers a few additional convenience features not available via the download. Don't worry if your JavaScript project doesn't otherwise involve Ruby or Rails, because the gem can be used in conjunction with any language! Begin by installing the gem:

$ gem install jasmine

Once installed, create and enter a new directory which we'll use as the basis for exploring Jasmine's capabilities, and then execute the following two commands:

Running the Example Specs

If you click the passed checkbox located at the top right of the page presented in Figure 1, you'll be provided with more details about the five sample tests (known as specs in Jasmine parlance) provided with the Jasmine example spec suite (Figure 2).

Let's use this class as the basis for exploring Jasmine's capabilities. Incidentally, if you're not familiar with JavaScript's prototype-based OOP design, see the article, Object-Oriented JavaScript Demystified.

The example specs which are used to validate the Player class' behavior are found in a file named PlayerSpec.js which is located in the spec/javascripts directory. While I certainly suggest you review PlayerSpec.js as it offers a great example of how to write a proper spec suite, consider first following along with the ensuing creation of our own suite, as it will serve to introduce several concepts demonstrated in the PlayerSpec.js suite.

Begin by creating a file named Play2Spec.js, saving it to the spec/javascripts directory. Add the following contents to this file:

So what does this spec accomplish? The spec starts by defining the expected behavior in plain English. In our case, we expect the hello world spec to pass. In order to test the behavior, the it method accepts a callback which defines the expectation. We are simply expecting the value true to be true.

So far, so good. Let's revise the Play2 spec to put the Player class through the ringer.

Testing the Player Class

When executed, the Player class' play method assigns values to the currentlyPlayingSong and isPlaying properties. Let's write a test to ensure the currentlyPlayingSong property is indeed set to the proper song. The song in question is an object instantiated via the Song class (found in the public/javascripts directory) passed into the Player class' play method

it('The Player is playing the proper song', function() {
var player = new Player();
var song = new Song();
player.play(song);
expect(player.currentlyPlayingSong).toEqual(song);
});

Refresh the browser and you'll see this spec does indeed pass. But we should also ensure that the isPlaying property is also set to true. You could perform the expectation test into the above spec, however perhaps breaking it out into its own spec is a better idea. When you create multiple specs intended to test one specific behavior (in our case, the behavior of the play method), you should create a suite.

Creating a Spec Suite

A spec suite is organized in a manner such that it is obvious a particular group of specs are intended to thoroughly test a particular behavior. This is done by nesting the related specs inside the describe() function:

describe("Play2", function() {
describe("when song is being played", function() {
it('The Player knows it is playing', function() {
var player = new Player();
var song = new Song();
player.play(song);
expect(player.isPlaying).toBe(true);
});
it('The Player is playing the proper song', function() {
var player = new Player();
var song = new Song();
player.play(song);
expect(player.currentlyPlayingSong).toEqual(song);
});
});
});

Refresh the browser and you'll see that not only have both tests passed, but they have also been grouped in such a way that it is apparent they are intended to test a specific behavior. But the specs aren't DRY, which will inevitably come back to bite us at one point. Use the beforeEach() function to solve this problem:

describe("when song is being played", function() {
beforeEach(function() {
player = new Player();
song = new Song();
});
it('The Player knows it is playing', function() {
player.play(song);
expect(player.isPlaying).toBe(true);
});
it('The Player is playing the proper song', function() {
player.play(song);
expect(player.currentlyPlayingSong).toEqual(song);
});
});

Conclusion

Of the various BDD frameworks I've used over the years, Jasmine is by far my favorite, offering an incredibly intuitive and natural testing approach. Hopefully this article helped to illustrate some of the features I find so appealing. Are you using Jasmine or other JavaScript testing frameworks? Tell us about your experiences in the comments!