To me there is no greater sin than writing code that doesn’t have tests. It is my belief that testing is not an option.1 Testing is required. I feel so strongly in that statement that I practice what is known as TDD.2 TDD, or test-driven development, is a philosophy of writing code that states you should always write your tests first and your code last. By practicing TDD you can sleep well at night, knowing that your code is well tested and that should anything crop up, you can quickly and easily fix it with the comfort and knowledge that you haven’t broken anything else.
The art of TDD can be daunting; a lot of people don’t know where to begin. If you need some guidance on how to become a test-driven developer, might I recommend a little blog post written by yours truly, titled “How to Become a Test-driven Developer.”3
In this chapter I want to take a quick look at what I consider to be one of the best testing tools out there for testing JavaScript applications—Jasmine.4
Jasmine isn’t the only testing tool for JavaScript; in fact, there are quite a few. I think Jasmine is one of the nicest ones around because it emulates my favorite Ruby testing framework, RSpec.5 Other JavaScript testing frameworks to check out include QUnit,6 JsTestDriver,7 and YUI Test.8
I won’t be covering Jasmine in great detail here. You can find a number of great articles and videos on the Internet covering Jasmine in more detail. Instead, I want to show you how to use Jasmine with CoffeeScript and to give you a feel for what it can do.
Typically, as you may remember from the beginning of this book, I don’t like to cover installation of tools in a book. The reason is pretty obvious—the instructions tend to be out of date by the time I finish typing, let alone by the time the book goes to print. The same is true here.
With that out of the way, I will point out what I consider to be the best way to install and set up Jasmine with CoffeeScript support. That would be to use the jasmine-headless-webkit9 Ruby gem. Because this is a Ruby gem it requires Ruby to be installed. It also features a few other dependencies. The link in the footnote has detailed instructions on how to get it set up.
There are ways of setting up Jasmine that aren’t as tricky. However, they don’t have native CoffeeScript support and require all sorts of precompilation of both your source files and your tests, and let’s be honest, who wants to deal with all of that?
If you don’t want to use jasmine-headless-webkit and instead use another version of Jasmine, that’s fine. For this chapter we will be using jasmine-headless-webkit, so I would recommend using that if you plan to follow along.
I’m going to assume that you have installed Jasmine and are ready to go. So let’s get started.
In this chapter we are going to build a simple calculator project. It will do the basic stuff that calculators do: add, subtract, multiply, and divide. Create a new folder for the project. Inside the project folder run the following command to set up Jasmine:
> jasmine init
When you do that, Jasmine should create a bunch of new files and folders in the project directory that look something like this:
public/
javascripts/
Player.js
Song.js
Rakefile
spec/
javascripts/
helpers/
SpecHelper.js
PlayerSpec.js
support/
jasmine.yml
jasmine_config.rb
jasmine_runner.rb
Jasmine throws a few example files in there to give us a feeling for how Jasmine works and to help us make sure that it has been set up and configured correctly. Let’s test that now:
> jasmine-headless-webkit -c
With any luck you should see output similar to the following:
Running Jasmine specs...
.....
PASS: 5 tests, 0 failures, 0.009 secs.
Great! We now have Jasmine up and running. Things look good. Well, everything except that awful command we have to execute on the command line whenever we want to run our tests. Let’s use what we learned in Chapter 7, “Cake and Cakefiles,” and write up a quick Cake task to help keep things simple.
First, I’ll show you the Cakefile and then I’ll explain it.
exec = require('child_process').exec
task "test", (options) =>
exec "jasmine-headless-webkit -c", (error, stdout, stderr)->
console.log stdout
(function() {
var exec,
_this = this;
exec = require('child_process').exec;
task("test", function(options) {
return exec("jasmine-headless-webkit -c", function(error, stdout, stderr) {
return console.log(stdout);
});
});
}).call(this);
Cakefile defines the following tasks:
cake test
This is definitely not the most complex Cakefile or Cake task you’ll ever see, but it does deserve a bit of explaining. The real magic of this Cake task is happening in the first line. In this line we import the child_process
module from the Node.js10 toolset. Don’t worry if you don’t understand what it’s doing or how it works; we’ll cover importing modules a bit more in Chapter 9, “Intro to Node.js.”
What we get with the child_process
module, in particular, is the exec
function. The exec
function lets us send commands to the console and then capture the output of that command when it finishes, which is exactly what we’re doing with our Cake task.
We’ve created a Cake task called test
that sends our command, jasmine-headless-webkit -c
, to the command line, where the tests are run. When the tests have finished, our callback function is executed, and the results of the tests print out to the console.
Now if you type the following in the console:
> cake test
you should see the results of the tests, just like we did earlier.
With the command line cleaned up a bit, let’s do some final configuration of our project before we dive into writing some tests.
First, we’ll clean up the project a bit and get rid of all those example files that were generated for us that we don’t want. You should delete files and directories until your project directory looks like this:
src/
Rakefile
spec/
javascripts/
helpers/
support/
jasmine.yml
jasmine_config.rb
jasmine_runner.rb
Okay, we are almost there. The last thing we need to do is configure the spec/javascripts/support/jasmine.yml
file to give it the specifics of our project:
src_files:
- "**/*.coffee"
helpers:
- "helpers/**/*.coffee"
spec_files:
- /**/*_spec.coffee
src_dir: "src"
spec_dir: spec/javascripts
Now we are finally ready to start looking at Jasmine code!
So what does a Jasmine test look like? Let’s build a simple one and you’ll see.
In our spec
directory, let’s create a new file called calculator_spec.coffee
. This file is where we will build all the tests, or specs as they’re sometimes known, for our calculator. Let’s first see what our simple test will look at, and then we’ll dissect it to understand better what it’s doing.
describe "Calculator", ->
it "does something", ->
expect(1 + 1).toEqual 2
expect(1 + 1).not.toEqual 3
(function() {
describe("Calculator", function() {
return it("does something", function() {
expect(1 + 1).toEqual(2);
return expect(1 + 1).not.toEqual(3);
});
});
}).call(this);
First, we need to create what is called a “describe” block. This is where we tell Jasmine what the “noun” is that we are going to be testing. We usually describe classes or functions. In this case we want to describe the Calculator
class we’re going to build. The first argument to the describe
function is a string that represents the noun “Calculator.” The second argument is going to be a function that will contain all the tests associated with that noun.
Inside the callback function we give to the describe
function we can define “it” blocks. An “it” block, which is just a function, is similar to the “describe” function, or block, in that it takes two arguments. The first argument is a string that represents what we plan on testing now. In this short example we want to test that the “Calculator” “does something.” The second argument is a function that contains the assertions to prove what it is we are testing.
In our “does something” test, we are asserting that 1 + 1
equals 2
. We are also rather triumphantly asserting that 1 + 1
does not equal 3
. We did these tests using what are called matchers
; in this case we used the toEqual
matcher. Matchers have one simple rule: if the matcher returns true
the test passes; otherwise, the test fails.
So what does the expect
function do and why do we need it? The expect
function takes as its argument the statement you want to test, so for us it would be 1 + 1
, and returns a special object that has the matcher functions on it. In a nutshell, it’s there to make Jasmine’s life a little easier. It also helps the readability of your tests a bit.
Jasmine ships with a handful of matchers that you can use to test almost anything. You can find a list of these matchers on the documentation11 site for Jasmine.
Now that we have a basic understanding of how to write a Jasmine test, let’s flesh out what the tests are going to look like for our Calculator
class. We’ll start by removing our example test and adding four describe
blocks to test addition, subtraction, multiplication, and division:
describe "Calculator", ->
describe "#add", ->
it "adds two numbers", ->
describe "#subtract", ->
it "subtracts two numbers", ->
describe "#multiply", ->
it "multiplies two numbers", ->
describe "#divide", ->
it "divides two numbers", ->
(function() {
describe("Calculator", function() {
describe("#add", function() {
return it("adds two numbers", function() {});
});
describe("#subtract", function() {
return it("subtracts two numbers", function() {});
});
describe("#multiply", function() {
return it("multiplies two numbers", function() {});
});
return describe("#divide", function() {
return it("divides two numbers", function() {});
});
});
}).call(this);
After looking at that code, you are probably wondering why there are multiple “describe” blocks under our initial “describe.” We do that because we are going to be writing tests for each of the four functions we’re going to have on our Calculator
class. Each of these functions is a noun that we are going to be testing. Below each of those nouns are the “it” blocks that state what we will be testing.
We can run our tests with this:
> cake test
The output of our tests should look like this:
Running Jasmine specs...
....
PASS: 4 tests, 0 failures, 0.02 secs.
There is a #
before each of the function names in our test because that is a common testing style that indicates that function is an instance-level function. If we were to describe class-level functions, we would preface the function name with .
instead of #
.
Our tests all pass because there is nothing inside of the “it” blocks. Let’s flesh them all out a bit:
describe "Calculator", ->
describe "#add", ->
it "adds two numbers", ->
calculator = new Calculator()
expect(calculator.add(1, 1)).toEqual 2
describe "#subtract", ->
it "subtracts two numbers", ->
calculator = new Calculator()
expect(calculator.subtract(10, 1)).toEqual 9
describe "#multiply", ->
it "multiplies two numbers", ->
calculator = new Calculator()
expect(calculator.multiply(5, 4)).toEqual 20
describe "#divide", ->
it "divides two numbers", ->
calculator = new Calculator()
expect(calculator.divide(20, 5)).toEqual 4
(function() {
describe("Calculator", function() {
describe("#add", function() {
return it("adds two numbers", function() {
var calculator;
calculator = new Calculator();
return expect(calculator.add(1, 1)).toEqual(2);
});
});
describe("#subtract", function() {
return it("subtracts two numbers", function() {
var calculator;
calculator = new Calculator();
return expect(calculator.subtract(10, 1)).toEqual(9);
});
});
describe("#multiply", function() {
return it("multiplies two numbers", function() {
var calculator;
calculator = new Calculator();
return expect(calculator.multiply(5, 4)).toEqual(20);
});
});
return describe("#divide", function() {
return it("divides two numbers", function() {
var calculator;
calculator = new Calculator();
return expect(calculator.divide(20, 5)).toEqual(4);
});
});
});
}).call(this);
Now we’re starting to get somewhere. We have nice looking tests that describe and test the four functions we’re going to have on our Calculator
class, so what happens if we run the tests?
Running Jasmine specs...
FFFF
Calculator #add adds two numbers. (../jasmine/calc.5/spec/javascripts/calculator_spec.coffee:5)
ReferenceError: Can't find variable: Calculator in ../jasmine/calc.5/spec/javascripts/calculator_spec.coffee (line ~6)
Calculator #subtract subtracts two numbers. (../jasmine/calc.5/spec/javascripts/calculator_spec.coffee:11)
ReferenceError: Can't find variable: Calculator in ../jasmine/calc.5/spec/javascripts/calculator_spec.coffee (line ~13)
Calculator #multiply multiplies two numbers. (../jasmine/calc.5/spec/javascripts/calculator_spec.coffee:17)
ReferenceError: Can't find variable: Calculator in ../jasmine/calc.5/spec/javascripts/calculator_spec.coffee (line ~20)
Calculator #divide divides two numbers. (../jasmine/calc.5/spec/javascripts/calculator_spec.coffee:23)
ReferenceError: Can't find variable: Calculator in ../jasmine/calc.5/spec/javascripts/calculator_spec.coffee (line ~27)
FAIL: 4 tests, 4 failures, 0.017 secs.
All tests are now failing, and they’re failing for a very good reason—we haven’t written our Calculator
class yet! So let’s do that; it’s a pretty simple class:
class @Calculator
add: (a, b) ->
a + b
subtract: (a, b) ->
a - b
multiply: (a, b) ->
a * b
divide: (a, b) ->
a / b
(function() {
this.Calculator = (function() {
function Calculator() {}
Calculator.prototype.add = function(a, b) {
return a + b;
};
Calculator.prototype.subtract = function(a, b) {
return a - b;
};
Calculator.prototype.multiply = function(a, b) {
return a * b;
};
Calculator.prototype.divide = function(a, b) {
return a / b;
};
return Calculator;
})();
}).call(this);
Now when we run our tests again we should see them all passing:
Running Jasmine specs...
....
PASS: 4 tests, 0 failures, 0.021 secs.
Our tests look pretty good now, but a lot of duplication occurs in each test. In each test we are creating a new instance of our Calculator
class. Jasmine will help us clean that up a bit using the beforeEach
function.
As you might guess, there is also an afterEach
function. The afterEach
is great for resetting databases, files, or other fixture data back to where it was before the test was run.
Let’s move the creation of an instance of the Calculator
class to a beforeEach
call. We do that by passing a function to the beforeEach
function that will be called before each “it” block in the current “describe” block, as well as any subsequent “describe” blocks.
describe "Calculator", ->
beforeEach ->
@calculator = new Calculator()
describe "#add", ->
it "adds two numbers", ->
expect(@calculator.add(1, 1)).toEqual 2
describe "#subtract", ->
it "subtracts two numbers", ->
expect(@calculator.subtract(10, 1)).toEqual 9
describe "#multiply", ->
it "multiplies two numbers", ->
expect(@calculator.multiply(5, 4)).toEqual 20
describe "#divide", ->
it "divides two numbers", ->
expect(@calculator.divide(20, 5)).toEqual 4
(function() {
describe("Calculator", function() {
beforeEach(function() {
return this.calculator = new Calculator();
});
describe("#add", function() {
return it("adds two numbers", function() {
return expect(this.calculator.add(1, 1)).toEqual(2);
});
});
describe("#subtract", function() {
return it("subtracts two numbers", function() {
return expect(this.calculator.subtract(10, 1)).toEqual(9);
});
});
describe("#multiply", function() {
return it("multiplies two numbers", function() {
return expect(this.calculator.multiply(5, 4)).toEqual(20);
});
});
return describe("#divide", function() {
return it("divides two numbers", function() {
return expect(this.calculator.divide(20, 5)).toEqual(4);
});
});
});
}).call(this);
The scope of beforeEach
and afterEach
calls can be a bit confusing. It helps to try to think of it a bit like a waterfall. The calls trickle down from the current “describe” block scope to all “describe” blocks that are nested below. It does this for as many levels of “describe” blocks there are.
When writing beforeEach
functions, it’s important to know that you can have as many as you want, and they can be at any level of your tests as you need. Let’s see this in action with our Calculator
class.
Let’s add a flag to our Calculator
that tells the calculator whether it should operate in scientific mode.
class @Calculator
constructor: (@scientific = false)->
add: (a, b) ->
a + b
subtract: (a, b) ->
a - b
multiply: (a, b) ->
a * b
divide: (a, b) ->
a / b
(function() {
this.Calculator = (function() {
function Calculator(scientific) {
this.scientific = scientific != null ? scientific : false;
}
Calculator.prototype.add = function(a, b) {
return a + b;
};
Calculator.prototype.subtract = function(a, b) {
return a - b;
};
Calculator.prototype.multiply = function(a, b) {
return a * b;
};
Calculator.prototype.divide = function(a, b) {
return a / b;
};
return Calculator;
})();
}).call(this);
Next, let’s add a test that asserts that its default state is not scientific mode:
describe "Calculator", ->
beforeEach ->
@calculator = new Calculator()
it "is not in scientific mode by default", ->
expect(@calculator.scientific).toBeFalse()
describe "#add", ->
it "adds two numbers", ->
expect(@calculator.add(1, 1)).toEqual 2
describe "#subtract", ->
it "subtracts two numbers", ->
expect(@calculator.subtract(10, 1)).toEqual 9
describe "#multiply", ->
it "multiplies two numbers", ->
expect(@calculator.multiply(5, 4)).toEqual 20
describe "#divide", ->
it "divides two numbers", ->
expect(@calculator.divide(20, 5)).toEqual 4
(function() {
describe("Calculator", function() {
beforeEach(function() {
return this.calculator = new Calculator();
});
it("is not in scientific mode by default", function() {
return expect(this.calculator.scientific).toBeFalse();
});
describe("#add", function() {
return it("adds two numbers", function() {
return expect(this.calculator.add(1, 1)).toEqual(2);
});
});
describe("#subtract", function() {
return it("subtracts two numbers", function() {
return expect(this.calculator.subtract(10, 1)).toEqual(9);
});
});
describe("#multiply", function() {
return it("multiplies two numbers", function() {
return expect(this.calculator.multiply(5, 4)).toEqual(20);
});
});
return describe("#divide", function() {
return it("divides two numbers", function() {
return expect(this.calculator.divide(20, 5)).toEqual(4);
});
});
});
}).call(this);
Running Jasmine specs...
....
PASS: 5 tests, 0 failures, 0.021 secs.
Now we’ll write another “describe” block to describe our Calculator
class when it’s in scientific mode and we’ll add a beforeEach
call in that describe block to create a new Calculator
that is in scientific mode. Let’s also write a test to assert that when we tell it to be in scientific mode, it actually is:
describe "Calculator", ->
beforeEach ->
@calculator = new Calculator()
it "is not in scientific mode by default", ->
expect(@calculator.scientific).toBeFalse()
describe "scientific mode", ->
beforeEach ->
@calculator = new Calculator(true)
it "is in scientific mode when set", ->
expect(@calculator.scientific).toBeTruth()
describe "#add", ->
it "adds two numbers", ->
expect(@calculator.add(1, 1)).toEqual 2
describe "#subtract", ->
it "subtracts two numbers", ->
expect(@calculator.subtract(10, 1)).toEqual 9
describe "#multiply", ->
it "multiplies two numbers", ->
expect(@calculator.multiply(5, 4)).toEqual 20
describe "#divide", ->
it "divides two numbers", ->
expect(@calculator.divide(20, 5)).toEqual 4
(function() {
describe("Calculator", function() {
beforeEach(function() {
return this.calculator = new Calculator();
});
it("is not in scientific mode by default", function() {
return expect(this.calculator.scientific).toBeFalse();
});
describe("scientific mode", function() {
beforeEach(function() {
return this.calculator = new Calculator(true);
});
return it("is in scientific mode when set", function() {
return expect(this.calculator.scientific).toBeTruth();
});
});
describe("#add", function() {
return it("adds two numbers", function() {
return expect(this.calculator.add(1, 1)).toEqual(2);
});
});
describe("#subtract", function() {
return it("subtracts two numbers", function() {
return expect(this.calculator.subtract(10, 1)).toEqual(9);
});
});
describe("#multiply", function() {
return it("multiplies two numbers", function() {
return expect(this.calculator.multiply(5, 4)).toEqual(20);
});
});
return describe("#divide", function() {
return it("divides two numbers", function() {
return expect(this.calculator.divide(20, 5)).toEqual(4);
});
});
});
}).call(this);
Running Jasmine specs...
......
PASS: 6 tests, 0 failures, 0.017 secs.
Before we wrap up the development of our Calculator
class, we can clean up our tests a bit more by using a custom matcher to test whether the calculator in question is in scientific mode. Jasmine provides a nice, simple way of letting us define our own matchers.
In our spec/javascripts/helpers
directory, let’s create a file called to_be_scientific.coffee
.
The names of the files in the spec/javascripts/helpers
directory don’t really matter, but I like to make sure they’re fairly descriptive. Using the name of the matcher we’re going to write in that file is a great way to easily find it later when you need to make changes to it.
Let’s add the following to that file:
beforeEach ->
@addMatchers
toBeScientific: ->
@actual.scientific is true
(function() {
beforeEach(function() {
return this.addMatchers({
toBeScientific: function() {
return this.actual.scientific === true;
}
});
});
}).call(this);
Custom matchers don’t need to be placed into their own files. You could define them all in one helper file. I like to write one per file. I find it makes my code base a littler cleaner and saner. You can also write one-off matchers in a “describe” block for a particular test should the whim strike you.
To write our custom matcher, the first thing we need to do is add a beforeEach
call that will get called before every test in our entire test suite. Inside of that beforeEach
call we want to call the built-in Jasmine function, addMatchers
, that does just what its name says. It takes an object that contains the names of the matchers you want to add and the function that represents each of those custom matchers. It is important that the custom matcher returns either true or false as the result. Remember earlier in the chapter when I said that in Jasmine if an assertion returns true
the test passes; otherwise, the test fails? This is where you define that behavior.
In our matcher, toBeScientific
, we are going to assert whether the Calculator
instance we are testing has the scientific
flag set on it and return true
or false
based on that flag.
With our custom matcher in place, we can update our tests to use it like so:
describe "Calculator", ->
beforeEach ->
@calculator = new Calculator()
it "is not in scientific mode by default", ->
expect(@calculator).not.toBeScientific()
describe "scientific mode", ->
beforeEach ->
@calculator = new Calculator(true)
it "is in scientific mode when set", ->
expect(@calculator).toBeScientific()
describe "#add", ->
it "adds two numbers", ->
expect(@calculator.add(1, 1)).toEqual 2
describe "#subtract", ->
it "subtracts two numbers", ->
expect(@calculator.subtract(10, 1)).toEqual 9
describe "#multiply", ->
it "multiplies two numbers", ->
expect(@calculator.multiply(5, 4)).toEqual 20
describe "#divide", ->
it "divides two numbers", ->
expect(@calculator.divide(20, 5)).toEqual 4
(function() {
describe("Calculator", function() {
beforeEach(function() {
return this.calculator = new Calculator();
});
it("is not in scientific mode by default", function() {
return expect(this.calculator).not.toBeScientific();
});
describe("scientific mode", function() {
beforeEach(function() {
return this.calculator = new Calculator(true);
});
return it("is in scientific mode when set", function() {
return expect(this.calculator).toBeScientific();
});
});
describe("#add", function() {
return it("adds two numbers", function() {
return expect(this.calculator.add(1, 1)).toEqual(2);
});
});
describe("#subtract", function() {
return it("subtracts two numbers", function() {
return expect(this.calculator.subtract(10, 1)).toEqual(9);
});
});
describe("#multiply", function() {
return it("multiplies two numbers", function() {
return expect(this.calculator.multiply(5, 4)).toEqual(20);
});
});
return describe("#divide", function() {
return it("divides two numbers", function() {
return expect(this.calculator.divide(20, 5)).toEqual(4);
});
});
});
}).call(this);
See how much cleaner that looks? Our custom matcher is pretty simple, but we could easily put a lot more logic in there to clean up several lines of code. For example, if our Calculator
class had a GUI, we could test in our toBeScientific
matcher that the scientific
flag was set and the GUI keyboard had switched to a scientific keyboard instead of the standard one.
There you have it—a very quick and dirty whirlwind tour of the Jasmine test framework. In this chapter we talked a bit about why testing is important and how test-driven development can make your life a little bit nicer. I hope that I was able to show you that TDD is easy to do and can be worthwhile.
We talked briefly about a few of the ways to install Jasmine, as well as how to configure it for the way we want to work, in particular using Jasmine with CoffeeScript. After we had Jasmine set up and running, we looked at what makes up a Jasmine test.
Next we defined our tests for our Calculator
class and saw them all fail because we had yet to write our implementation of the class itself. After we wrote the implementation, we saw our tests all pass.
Finally we did several iterations of our specs and learned how to use beforeEach
hooks and how to write custom matchers that are more expressive for our code base.
I hope you enjoyed this quick tour of Jasmine. There are plenty of third-party libraries that can help you to write better tests, including ones that help you test your UI elements effectively. A quick search of GitHub12 will help you find some great little libraries that inspire your tests.
One last thing before I end this chapter: I want you to promise me right here and now that you will write tests for all of your code, whether that code is written in CoffeeScript, JavaScript, Java, ColdFusion, or Cobalt. Hold up your hand and say the following oath:
"
I solemnly swear to test all of my code.
I will not test just part of my code, but rather all of it.
I understand that failure to test my code
will result in Mark finding me and beating me with my own shoes.
I do this, not just for me,
but for all developers who have to work with my code base.
I also pledge to make other developers take this pledge.
Should they refuse to take this pledge I will tell Mark
and he will beat them with their own shoes.
Viva La Tests!
"
Congratulations. Now go forth and test!
1. http://www.metabates.com/2010/07/01/testing-is-not-an-option/
2. http://en.wikipedia.org/wiki/Test-driven_development
3. http://www.metabates.com/2010/10/12/how-to-become-a-test-driven-developer/
4. http://pivotal.github.com/jasmine/
5. https://github.com/rspec/rspec
6. http://docs.jquery.com/QUnit
7. http://code.google.com/p/js-test-driver/
8. http://yuilibrary.com/yui/docs/test/
9. http://johnbintz.github.com/jasmine-headless-webkit/
13.59.79.176