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:
$ jasmine init
Jasmine has been installed with example specs.
...
$ rake jasmine
(in /var/www)
your tests are here:
http://localhost:8888/
To make sure the Jasmine server is properly running, head over to http://localhost:8888/
. You should see the same output as that presented in Figure 1.
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).
These specs correspond to the Player.js
class, found in the newly created public/javascripts
directory. This class is representative of a JavaScript-based music player. Here’s the code:
function Player() {
}
Player.prototype.play = function(song) {
this.currentlyPlayingSong = song;
this.isPlaying = true;
};
Player.prototype.pause = function() {
this.isPlaying = false;
};
Player.prototype.resume = function() {
if (this.isPlaying) {
throw new Error("song is already playing");
}
this.isPlaying = true;
};
Player.prototype.makeFavorite = function() {
this.currentlyPlayingSong.persistFavoriteStatus(true);
};
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:
describe("Play2", function() {
it('Hello world test passes', function() {
expect(true).toBe(true);
});
});
Save the file and refresh the browser. You’ll see that the Play2
suite has been added to the list of running specs (Figure 3).
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
.
it('Hello world test passes', function() {
expect(true).toBe(true);
});
Change one of the compared values to false
and refresh the browser. You’ll see that the spec fails, as depicted in Figure 4.
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!