As you know if you’ve read my previous posts, I’m still wrapping my head around this whole development thing. Having worked on websites for a long time from the product side, I hear engineers talk about things like BDD, writing tests, etc and how it’s definitely the right way to go. As such, I’m striving to do things the ‘right way’

After poking around through a few different NodeJS testing frameworks, I settled on Jasmine (mainly because we use it at Bookish and I liked how the specs look when written in Coffeescript). There’s a npm module called jasmine-node that gives you the basic toolset you need to get Jasmine tests working, but it unfortunately doesn’t come with any good documentation on how to setup your test runner or anything like that. Here’s the best way I’ve found to run tests from the command line so you can run them locally or from your CI environment.
Step 1: Install jasmine-node
Note: This requires npm. If you need help installing npm, click here
Installing jasmine-node is actually quite simple. Just use the following command from your project’s base directory:
npm install jasmine-node
This will create a folder under ./node_modules/ called ‘jasmine-node’ that contains the command line version of jasmine which is compatible with nodeJS. If you want to check out the jasmine-node source tree before installing, take a look at github repo.
Step 2: Create your test runner
This is where, in my opinion, the jasmine-node docs fall short. Installing jasmine-node is a cinch, but you have no idea where to go from there. The first thing I figured out is that jasmine-node just gives you the simple executable that will output the results of all of your tests as well as the framework for testing, but it doesn’t give you the actual script to run the damn tests
As such, I’ve stolen a really great test runner script for jasmine-node. I can’t remember where I got it from, but it should work for you. I call this file ‘runtests.coffee‘ and place it in my ./tests/ directory:
./tests/runtests.coffee (download)
jasmine = require 'jasmine-node'
sys = require 'sys'
for key in jasmine
global[key] = jasmine[key]
isVerbose = true
showColors = true
coffee = true
process.argv.forEach (arg) ->
switch arg
when '--color' then showColors = true
when '--noColor' then showColors = false
when '--verbose' then isVerbose = true
when '--coffee' then coffee = true
jasmine.executeSpecsInFolder __dirname + '/spec', ((runner, log) ->
if runner.results().failedCount == 0
process.exit 0
else
process.exit 1
), isVerbose, showColors
As you can see, the testrunner loads jasmine, parses the command line parameters, and then runs each spec file in the ./spec directory.
Once you’ve placed your testrunner at ./spec/runtests.coffee and run it through the coffeescript compiler, you can make sure it works by running the following command from the base directory of your project:
node ./tests/runtests.js
If something’s busted, node will throw an exception and otherwise, you’ll see something like 0 tests, 0 assertions, 0 failures Nice!
Bonus points: To create a simple cake command to run your tests, add it to your Cakefile! (example)
Step 3: Writing your first spec
Most testing frameworks have the concept of a test suite and jasmine is no different. Each file defined under ‘./spec‘ represents a set of tests and within that set of tests, you can create a number of features, a number of tests for each feature, and a number of assertions for each test. If that last sentence sounded like jibberish, check out the jasmine suites and specs documentation. I won’t go into different spec-writing philosophies, as I’m still learning that myself.
To create a spec, you need to make a new file under ./spec Older versions of jasmine-node required you to have ‘spec’ in the filename but that doesn’t seem to be the case any more.
Let’s start with an example from one of my projects, node-towerd, a tower defense game written in node.
In my game, there is a controller called ‘world’ which runs the show. It proxies events to and from game objects as well as to and from the client. Within the world, there are a number of game entities like maps, mobs, and towers. Let’s take a look at the spec for towers:
./tests/spec/towerspec.coffee (download)
### Tower Tests ###
basedir = '../../'
App = require basedir + 'app.js'
Tower = (require basedir + 'controllers/towers.js').Tower
TowerModel = require basedir + 'models/tower-model.js'
Mob = (require basedir + 'controllers/mobs.js').Mob
MobModel = require basedir + 'models/mob-model.js'
Obj = (require basedir + 'controllers/utils/object.js').Obj
# Unit Tests
describe 'Towers towers.js', ->
beforeEach ->
world = new Obj # Required because maps relies on 'world' for some events
# Stub data
@name = 'Cannon Tower'
@id = 'cannon'
@active = 1
@symbol = 'C'
@damage = 1
@range = 2
@fakeMob = new Obj # Load a fake mob to emit events
@fakeMob.symbol = 'm'
@tower = new Tower @id, world
it 'Loads a new tower called Cannon Tower', ->
expect(@tower.id).toEqual(@id)
expect(@tower.name).toEqual(@name)
expect(@tower.active).toEqual(@active)
expect(@tower.damage).toEqual(@damage)
expect(@tower.range).toEqual(@range)
it 'Saves itself to the DB once loaded', ->
TowerModel.find { id: @id }, (err, res) =>
expect(res[0].name).toEqual @name
it 'Spawns itself on the map at 5, 4', ->
@tower.on 'spawn', (type, x, y, callback) =>
expect(@tower.x).toEqual(5)
expect(@tower.y).toEqual(4)
@tower.spawn 5, 4, (callback) ->
it 'Finds no targets when none are in range', ->
fakeWorld = new Obj
# Spawn the tower
@tower.spawn 5, 4, (callback) ->
# Spawn a fake mob
fakeMob = new Mob 'warrior', fakeWorld
fakeMob.spawn 0, 0, (callback) ->
@tower.checkTarget fakeMob, (res) ->
expect(res).toEqual []
it 'Fires on a target when one is in range', ->
fakeWorld = new Obj
# Spawn the tower
@tower.spawn 5, 4, (callback) ->
# Spawn a fake mob
fakeMob = new Mob 'warrior', fakeWorld
fakeMob.spawn 5, 5, (callback) ->
@tower.checkTarget fakeMob, (res) ->
expect(res.id).toEqual 'warrior'
This is a relatively large spec, and I’m sure I’m doing a ton of things wrong, but it should give you a good idea of how most specs are structured. You start by loading all of your dependencies via node’s require command. Note: you don’t have to require jasmine-node, the test-runner takes care of that.
Describe your feature
Next, you describe each feature with a describe 'featurename', -> anonymous function. This lets jasmine know that we’re going to start defining a new feature. If any tests under ‘featurename’ fail, jasmine will report back so you know where to start debugging.
Before and After
Once we’ve described our feature, jasmine lets us group any logic that needs to take place repeatedly before each individual test runs via the beforeEach feature. It’s usage is really simple. If you need to create new objects, reset variables, or wipe your database, just create a function called beforeEach or afterEach like so: beforeEach ->
Tests
Finally, let’s write the damn test! Defining each test is as simple as creating a new function that starts with ‘it’ and writing out what you expect the feature to do in plain english. For example:
it 'Loads a new tower called Cannon Tower', ->
expect(@tower.id).toEqual(@id)
expect(@tower.name).toEqual(@name)
expect(@tower.active).toEqual(@active)
expect(@tower.damage).toEqual(@damage)
expect(@tower.range).toEqual(@range)
Within each ‘it’ function, jasmine supports a number of ‘matchers’ that allow you to evaluate whether the feature is working as intended or not. In your jasmine-node reports, these matchers are called ‘assertions.’ In the above example, we make sure that a number of variables are equal to what we’re expecting. You can use matchers/assertions to check if variables are defined (or not defined), make sure a variable is set to a certain value, or even check if an exception is thrown. For more info, you should read the full list of jasmine matchers.
Wrapping up
So you’ve installed jasmine-node, created your test runner, created your first spec, and defined your first feature! You can now run your test suite and see if everything passes.
Warning! When writing asynchronous tests, jasmine-node will not automatically wait for your asynchronous functions and callbacks to run. You need to tell it to wait a callback using the asyncSpecDone() and asyncSpecWait() commands. Their usage is as follows:
describe 'Customers', ->
it 'Should save a customer\'s address', ->
var customer = new Customer()
customer.save (err) ->
expect(err).not.toBeNull()
expect(customer.name).toEqual('bob')
asyncSpecDone() # Tell jasmine that our Async function has finished executing
asyncSpecWait() # Tell jasmine to wait for the above function to finish executing
To see more example specs from my project node-towerd, click here. To read more about writing specs in jasmine, click here.
Coming soon: Continuous Deployment in NodeJS with Jasmine and Jenkins (once I actually get it working!)