Getting started with tests takes time because there are many small decisions to make. If you haven't gone through the whole setup and evolution more than once or twice, it's easy to get lost or just plain forget.
This post aims to walk through the creation of a TDD boilerplate project for re-use when driving your JavaScript tests. We'll set up the project to work with GitHub, node.js, mocha, rawgit, testem and travis. All steps were re-learned the hard way while I worked through my own where.js project in January-February 2014.
Special thanks to these wonderful people for their patient attention to detail and their marvelous feedback
- Toby Ho, creator of testem
- Jamon Holmgren
- John B. Roberts
Update: 25 NOV 2014 ~ links to rawgithub have been modified to point to the rawgit.com domain.
Cross-platform emphasis
The JavaScript you'll see here is designed (hacked) to run on both node.js and in the browser without using a special loader or packager like browserify or require.js. The point is that we first get the project started and get comfortable with the TDD workflow in both runtimes.
How long?
60-90 minutes, depending on what you have already set up, and how many of the suggested breaks you take.
tl;dr
Here's my own version of the project we're going to build ~ dfkaye/tdd-boilerplate
Workflow
This sequence seems long, as there are several decisions to make. We'll do
just enough so you can see the process itself take shape. The most important
sections are those on mocha
and testem
. However, the sequence is done
in an order that shakes out the structural kinks in the workflow.
Between each section, I recommend taking a break before moving on.
set up GitHub
We'll start by setting up the project on your system, then on GitHub. While these steps can be done separately, I recommend doing them together - right away - so you can verify that committing your changes actually works.
set up node.js
After GitHub, we'll set up node.js, a server-side runtime for JavaScript, to drive our tests and development.
add mocha tests
We'll use npm, included with node.js, to install mocha, a very flexible JavaScript test runner by TJ Holowaychuk, and start driving some tests.
add npm test
We'll add a way to drive the suite through node.js without specifying node in the command each time.
add browser suite
We'll create a browser suite for mocha that we can view as a standalone or static page.
add rawgit link
We'll look at using rawgit, a proxy service originally set up by Ryan Grove, for loading static pages from your GitHub repo, so you can view the running browser suite online.
add testem
We'll use npm to install testem, a node.js socket-based test harness developed by Toby Ho, for both node and cross-browser development to drive all our suites simultaneously.
add travis hook
We'll add support for travis, a remote service that runs your projects tests each time you commit to GitHub.
next steps
Short list of other test libraries and projects.
----------------------- GET STARTED ------------------------
GitHub repo
If you've not used it before, GitHub is an online repository for storing and sharing your projects using git, the distributed version control system. To use them you will need to install git or msysgit from GitHub
(I leave the setup details as an exercise for the reader.)
Once you're set up with git and GitHub, open your command line shell or git-bash terminal.
Create the local project directory with git files
We need a place to start locally so let's create the directory and initialize it for use with git and GitHub.
On the command line create and initialize the project folder, e.g.,
mkdir tdd-boilerplate
cd tdd-boilerplate
git init
You should see a message like Initialized empty Git repository in
[path/to]/tdd-boilerplate/.git/
Create the remote project on GitHub
GitHub makes setting a up new project pretty easy. We'll use the online service to do that.
Open a browser and create the new project on GitHub
- name it 'tdd-boilerplate'
- add a short description (you can change this later)
- choose public, not private (show your work)
- select
initialize with a README
- add .gitignore file - choose
Node
- add a license - choose
MIT
(you can change this later) - click
Create Repository
You should be redirected your new tdd-boilerplate project page.
Note
The .gitignore
file should have a handful open of entries - these are files or
directories that git will ignore whenever your add, commit, and push changes.
In this case, by choosing Node
, we'll have entry for node_modules
so that
any npm modules we install won't be pushed to GitHub.
Sync up with GitHub
We need the remote files we just created to be sync'd with our local directory. Git will detect the changes that we're about to make to our local files and can then commit those to the remote repository at GitHub.
First we need to tell git about our remote repo and assign it as origin
. Enter
the following at the command line, replacing YOUR_NAME
with your GitHub
username:
git remote add origin git@github.com:YOUR_NAME/tdd-boilerplate.git
Now pull down the license
, README
and .gitignore
files from GitHub
git pull origin master
You should see a message similar to this
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (5/5), done.
From github.com:YOUR_NAME/tdd-boilerplate
* branch master -> FETCH_HEAD
Let's add a file to the project with nothing in it
touch index.js
We'll fill this in eventually with actual JavaScript. Right now we just want to verify we can commit successfully to the remote repository.
Commit our changes to git, and push them to GitHub.
git status
git add .
git commit -am 'first commit; sync up'
git push -u origin master
You should see a success message ending with something like
025cf88..103f972 master -> master
Branch master set up to track remote branch master from origin.
Verify this check-in
Open the repo on github.com (https://github.com/YOUR_NAME/tdd-boilerplate), and
verify the index.js
file is listed.
Now we're in sync.
addendum: .git/config
Working on this project and receiving enlightening feedback from reviewers, I
added this short bit to explain the .git/config
file's contents.
When we initialized the project with git init
, git added several directories
and files for itself to track our changes and so forth.
Open the file at /tdd-boilerplate/.git/config
. Depending on your git client
settings, the first section should resemble
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
hideDotFiles = dotGitOnly
Verify these lines appear
[remote "origin"]
url = git@github.com:YOUR_NAME/tdd-boilerplate.git
fetch = +refs/heads/*:refs/remotes/origin/*
This was added by git when we used the git remote add origin
command.
Verify these lines appear
[branch "master"]
remote = origin
merge = refs/heads/master
This was added by git when we used the git push -u origin master
command.
While you can modify this file directly if you wish, be sure to consult GitHub documentation about Adding a Remote for more information.
----------------------- BREAK TIME ------------------------
node.js
If you haven't used it, go get node.js, a server-side runtime for JavaScript.
(I leave the setup details as an exercise for the reader.)
Once you're set up with node.js, open your command line shell or git-bash terminal.
Configure the npm package
Every node.js module or project should have a package descriptor file, named
package.json
, which makes it re-usable by other developers as well as services
such as travis which we'll see later.
Use npm init
to create the
package.json
file at the root of the project. This utility prompts you at the
command line for a title, description, and some other things. Just get through
enough steps to create the file.
cd tdd-boilerplate
npm init
npm init
will output the package.json file text before saving. You should see
something like this
{
"name": "tdd-boilerplate",
"version": "0.0.0",
"description": "TDD boilerplate for JavaScript projects",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git://github.com/YOUR_NAME/tdd-boilerplate.git"
},
"keywords": [
"tdd",
"javascript",
"boilerplate",
"testem",
"rawgithub",
"rawgit"
],
"author": "YOUR_NAME <YOUR@EMAILADDRESS.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/YOUR_NAME/tdd-boilerplate/issues"
}
}
Is this ok? (yes)
Type yes
to save.
Commit our changes to git, and push them to GitHub.
git status
git add .
git commit -am 'add package.json'
git push
----------------------- BREAK TIME ------------------------
mocha suite
We'll use npm
to install mocha, the
JavaScript test runner. Then we'll create an actual test and verify that we can
run it from the command line.
Install mocha
At the command line:
cd tdd-boilerplate
npm install mocha --save-dev
This will install mocha to a node_modules
directory in the project, and add
mocha as a devDependency
to the package.json file.
Note
The node_modules
directory should be listed in the .gitignore
file we
created when getting started with GitHub. That entry prevents the
node_modules
directory from being checked in and pushed to GitHub.
Create test
directory for mocha
and some placeholder test files
We'll create a test that can be run from either node or the browser. We'll then create a node-specific suite to drive the mocha/suite.
mkdir test test/mocha
touch test/mocha/suite.js
touch test/mocha/node-suite.js
Create a test
Open test/mocha/suite.js
and add the following boilerplate:
// mocha/suite.js
var boilerplate;
if (typeof require == 'function') {
// enable to re-use in a browser without require.js
boilerplate = 'boilerplate';
}
describe('smoke test', function() {
it('should pass', function () {
assert.equal(boilerplate, 'boilerplate', 'failure message');
});
});
Create the node-suite
We'll create the node-specific suite for our test that drives mocha
programmatically. That will allow us to use npm test
command further down
which will give us a way to add specific test commands to our package.json
file.
Open test/mocha/node-suite.js
and add the following to load the mocha tests:
// test/mocha/node-suite.js
// This runs with mocha programmatically rather than from the command line.
// how-to-with-comments taken from https://github.com/itaylor/qunit-mocha-ui
//Load mocha
var Mocha = require("mocha");
//Tell mocha to use the interface.
var mocha = new Mocha({ui:"bdd", reporter:"spec"});
//Add your test files
mocha.addFile("./test/mocha/suite.js");
//Run your tests
mocha.run(function(failures){
process.exit(failures);
});
Verify it from node.js
Tell node.js to run the node-suite by the following command
node ./test/mocha/node-suite.js
You should see an error message
$ node ./test/mocha/node-suite.js
.
0 passing (4ms)
1 failing
1) smoke test should pass:
ReferenceError: assert is not defined
Aha! We need to require the assert
module that ships with node.js first
// mocha/suite.js
var boilerplate;
// ... ADD THIS LINE ...
var assert;
if (typeof require == 'function') {
// enable to re-use in a browser without require.js
boilerplate = 'boilerplate';
// ... ADD THIS LINE ...
assert = require('assert');
}
describe('smoke test', function() {
it('should pass', function () {
assert.equal(boilerplate, 'boilerplate', 'failure message');
});
});
Re-run the test
node ./test/mocha/node-suite.js
That should pass
$ node ./test/mocha/node-suite.js
.
1 passing (4ms)
Create the code to be tested
touch index.js
Open the index.js
file and add the following
IIFE
// main boilerplate
;(function () {
// node.js
if (typeof module != "undefined") {
module.exports = boilerplate;
}
// browser
if (typeof window != "undefined") {
!window.boilerplate && (window.boilerplate = boilerplate);
}
function boilerplate() {
return 'boilerplate';
}
}());
Note The node-vs-browser assignment blocks are useful for getting started with tests that can run on either environment. You can replace that later with a loading library of your choice.
Open the mocha/suite.js
file and change our boilerplate assignment to loadindex.js
:
if (typeof require == 'function') {
// enable to re-use in a browser without require.js
// ... COMMENT OR REMOVE THIS LINE...
//boilerplate = 'boilerplate';
// ... ADD THIS LINE ...
boilerplate = require('../../index.js');
assert = require('assert');
}
Re-run the node suite
node ./test/mocha/node-suite.js
You should see an error message
$ node ./test/mocha/node-suite.js
.
0 passing (4ms)
1 failing
1) smoke test should pass:
AssertionError: failure message
Aha! we need to modify the reference to boilerplate()
as a method call
in our assert statement.
describe('smoke test', function() {
it('should pass', function () {
// ... COMMENT OR REMOVE THIS LINE...
//assert.equal(boilerplate, 'boilerplate', 'failure message');
// ... ADD THIS LINE ...
assert.equal(boilerplate(), 'boilerplate', 'failure message');
});
});
Re-run the test
node ./test/mocha/node-suite.js
That should pass.
Commit our changes to git, and push them to GitHub.
git status
git add .
git commit -am 'first mocha test and node suite'
git push
----------------------- BREAK TIME ------------------------
npm test
The npm test command allows us to
run specific tests in our project without having to specify node
in the
command line. That lets other programs and services exercise our tests more
easily.
npm scripts
entry
Add an entry in package.json
as
"scripts" : {
"test": "node test/mocha/node-suite.js"
},
including the trailing comma if it's not the last entry. The test
entry
should be present with an echo
statement if you followed the npm init step
above.
Now try the test
command to run the mocha tests on node.js
npm test
Should see mocha's spec reporter listing the single passing smoke test
.
...
smoke test
v should pass
1 passing (8ms)
Commit our changes to git, and push them to GitHub.
git status
git add .
git commit -am 'npm test script'
git push
----------------------- BREAK TIME ------------------------
browser suite
In this section we'll create an HTML file that runs our tests with mocha, and which we can view as a static page. We'll modify it later to work with testem.
To run mocha in the browser you'll need mocha.js
and mocha.css
files that
are separate from mocha's node.js bundle.
Add mocha.css and mocha.js vendor files
Create a /vendor/mocha
directory
cd tdd-boilerplate
mkdir vendor vendor/mocha
Fetch these files and save them in the vendor/mocha directory.
If you have curl
you can do these
curl https://raw.githubusercontent.com/visionmedia/mocha/master/mocha.css > ./vendor/mocha/mocha.css
curl https://raw.githubusercontent.com/visionmedia/mocha/master/mocha.js > ./vendor/mocha/mocha.js
else you can navigate to the files directly in a browser and copy+paste their content to the respective target file:
- copy https://raw.githubusercontent.com/visionmedia/mocha/master/mocha.css to
/vendor/mocha/mocha.css
- copy https://raw.githubusercontent.com/visionmedia/mocha/master/mocha.js to
/vendor/mocha/mocha.js
Be Advised When copying the file text into your editor, be sure FIRST to set the file's encoding to UTF-8. ASCII encoding will force conversions of entities, such as the checkmarks, to question marks :(
Create an HTML file for our browser suite
touch test/mocha/browser-suite.html
Open the test/mocha/browser-suite.html
file and add the following
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>tdd-boilerplate mocha browser-suite</title>
<link rel="stylesheet" href="../../vendor/mocha/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="../../index.js"></script>
<script src="../../vendor/mocha/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<!-- get tests and start -->
<script src="./suite.js"></script>
<script>
mocha.checkLeaks();
mocha.globals(['boilerplate']); // watch our boilerplate function
mocha.run();
</script>
</body>
</html>
Run the browser tests
Open this file in your browser to verify file paths are correct (mocha runs, the styles are correct).
Hold it! You should see a failing test as there is no assert
module
in the browser.
Add assert.js
Fetch the browser version of assert
and place it in the /vendor directory
directory
curl https://raw.githubusercontent.com/Jxck/assert/master/assert.js -o ./vendor/assert.js
or copy https://raw.githubusercontent.com/Jxck/assert/master/assert.js to /vendor/assert.js
Reminder When copying the file text into your editor, be sure FIRST to set the file's encoding to UTF-8. ASCII encoding will force conversions of entities, such as the checkmarks, to question marks :(
Open browser-suite.html
and add this script tag just before mocha/mocha.js
<script src="../../vendor/assert.js"></script>
Re-load the browser-suite in your browser to verify that the single test passes.
Commit our changes to git, and push them to GitHub.
git status
git add .
git commit -am 'browser suite; mocha files; assert; '
git push
----------------------- BREAK TIME ------------------------
rawgit
This optional step in the process is very short but worth considering for a moment.
I like using rawgit (formerly rawgithub) to confirm that the project's browser tests really are accessing the correct file paths remotely, and to share these tests with others online.
Add rawgit link to the browser suite on your README page
Open the README page and add the following markdown
# rawgit
+ [mocha suite](https://rawgit.com/YOUR_NAME/YOUR_PROJECT/master/test/mocha/browser-suite.html)
Commit our changes to git, and push them to GitHub.
git status
git add .
git commit -am 'rawgit'
git push
View the browser suites on rawgit
Give it a moment, then view the project page on GitHub and visit the rawgit page via the new link.
----------------------- BREAK TIME ------------------------
testem
The testem
harness makes cross-browser TDD extremely fast. In this section
we'll add testem to our workflow and use it to run all tests on node and in
multiple browsers.
Install testem
testem is a npm module so we can install it easily
npm install testem -g
Use the -g
flag to install testem globally, so you can re-use it easily on
other projects.
Refactor the browser suite for use with testem
Open test/mocha/browser-suite.html
and add the following inline script block
(between mocha.setup and start comment)
...
<script>
if (location.href.indexOf('://localhost:7357') != -1) {
// testem goes between test runner setup and the tests
document.write('<script src="/testem.js">' + '<\/' + 'script>');
}
</script>
<!-- get tests and start -->
...
Try it with testem
Tell testem to run, forwarding the browser to the URL of our test page
testem -t ./test/mocha/browser-suite.html
You should see testem open in the shell waiting for tests.
Open a browser manually
You can open any browser manually and go to http://localhost:7357
The browser should forward to
http://localhost:7357/test/mocha/browser-suite.html
and you should now see the
browser suite with results, and see that testem has printed the results in the
shell.
Hit q
to stop testem.
Open a browser with a testem launcher
You can use a testem launcher
for the particular browser you want to try. To
open the test automatically in chrome, start testem by running the following:
testem -l chrome
Chrome should open and forward to
http://localhost:7357/test/mocha/browser-suite.html
and you should now see the
browser suite with results, and see that testem has printed the results in the
shell.
Hit q
to stop testem.
Configure testem to launch the node.js suite with a custom launcher
We'll define a custom launcher for testem that runs our node suite. To do that
we need a configuration file for testem, called testem.json
.
Create the testem.json
file
touch testem.json
Add the following
{
"launchers": {
"mocha" : {
"command": "node ./test/mocha/node-suite.js"
}
}
}
Verify this works by starting testem from the command line
testem -l mocha
You should see the node test passing in the shell. If your browser is still open you should also see the browser suite passing in the shell.
Hit q
to stop testem.
Configure npm scripts to run testem in the browser
Now we'd like to run both node.js and browser-suite in testem at the same time.
To set that up, add the testem
entry in the scripts
section of your
project's package.json
file:
"scripts" : {
"test": "node ./test/mocha/node-suite.js",
"testem": "testem -l mocha -t ./test/mocha/browser-suite.html"
}
Note that the testem
entry contains both the mocha
launcher and the -t
flag for the browser suite.
Start both node and browser suites with testem from the command line
npm run testem
You should see the passing test in both node and browser.
Commit our changes to git, and push them to GitHub.
git status
git add .
git commit -am 'testem, scripts, launcher commands'
git push
----------------------- BREAK TIME ------------------------
travis
Once you have several tests in place and have made several commits to GitHub, it's nice to have a service that runs tests for you on check-in. Travis is just such a service, allowing you continuous verification. This is especially important when you are working with others who make regular updates to the project.
Set the project to notify travis
Visit the travis profile page to set up travis to be notified by GitHub every time you commit something to your repo. You will be prompted to sign in with your GitHub credentials if you haven't done this before.
On the travis profile, visit the Accounts page (mouseover your gravitar at the upper right - the Accounts link will appear).
Find the tdd-boilerplate
repo in the list that appears. If it's not there,
click Sync Now
to refresh travis from GitHub. When you find the repo listed,
click the Off
button to the On
state.
Now travis will be able to run the npm test
command on your repo every time
you push changes to GitHub.
Verify the service hook for travis on GitHub
- go to the service hooks page for the project at
https://github.com/YOUR_NAME/tdd-boilerplate/settings/hooks
- select
Configure Services
- should find
travis
in the list that appears - it should bechecked
Configure travis for your project
Create a .travis.yml
file (leading dot included)
touch .travis.yml
Enter the following for travis to run the node.js tests:
language: node_js
node_js:
- "0.11"
- "0.10"
- "0.08"
You don't need multiple node.js versions. I add more than one in case of API changes I've missed.
Add a travis build status badge
This is nice to have on your project's README as it's not only an image but a link to the travis test result.
Here's the build status link/badge for my version of tdd-boilerplate:
Copy, paste and modify the following at the top of your version of the README
[![Build Status](https://travis-ci.org/YOUR_NAME/tdd-boilerplate.png?branch=master)](https://travis-ci.org/YOUR_NAME/tdd-boilerplate)
Commit our changes to git, and push them to GitHub.
git status
git add .
git commit -am 'travis hook'
git push
Travis should notify you by email whether the build passed.
You can also go to the project page on GitHub and visit the travis profile page via the travis badge image. There you can see the test run for the last commit.
----------------------- CELEBRATE ------------------------
Voilà!
You now have a TDD boilerplate to get started testing JavaScript projects, complete with travis, npm test commands, testem launchers, and re-usable browser suite viewable as standalone, or on rawgit, or with testem.
You can start moving things around to your liking, clean up the relative paths
in the browser suite, for example, or move the node_modules
directory out of
the project (I usually put it in a parallel directory to all my projects), or
go with your preferred loader solution if you're doing things more for the
browser than for node.js. And so on…
Next
You can inspect my implementation of all the steps we took in my own version of this project at https://github.com/dfkaye/tdd-boilerplate.
You can log issues there regarding any mistakes or confusion this long post may have caused.
Visit the tests section of my where.js project to see commands and implementations of this approach using other test runners, including
Visit this example testem-requirejs-mocha project by @teppeis for details on adding require.js to your setup.