JS Testing with Mocha, 101
Javascript testing? Why not? We unit test everything else (or should be). Only testing HTML, say, through Selenium, doesn't test Javascript in isolatio, potentially leaving caverns in code coverage, making it harder to track down issues or worse, lets through avoidable bugs into production. That is of course, far too late to discover you have them. QAs take note, does your team cover their Javascript code?
Blah...
As with most things in programming, Javascript testing is as much shaped by our goal, as how we run it. If we want JS tests to form part of an automated testing process, integrating with Jenkins, TeamCity, TFS or "Insert your favourite task runner or Continuous Integration server here" we will likely end up working with mocha, and either Jasmine for in-browser tests or perhaps Karma, using PhantomJS and built on top of node JS. It is mocha I am going to cover in this artice. If we wanted to build our JS tests into a web-page, to tack on to something like Fitnesse (remember that?) then we would need Jasmine and the Jasmine test page to load and run the tests.
In this article, we're going to set up mocha together with an assertion library, then write the tests to verify the software is doing what we think it should, before we then write the code to verify it. This approach is the cornerstone of 'Test Driven Development' and it really lives up to its name.
Want to follow along?
We'll need the following ingredients:
- Node JS
- Mocha JS testing framework and the 'assert' assertion library (bundled with mocha)
- Windows, Linux or OSX
- Working in a CLI (Windows: Command Prompt or Cygwin. Linux/Mac: Bash)
- Working knowledge of Javascript - "Object Oriented" Javascript not essential
- Familiarity with TDD is an advantage
The Recipe
Step 1: Install the Environment
NodeJS
Let's get started. If you haven't already, first install your system's flavour of node JS from;
Tip: Windows Users
I let Node add its directory into the PATH, allowing me to use it from any Command Window. You're given a checkbox for this during installation. Definitely check it! This intro assumes you have. You'll know if you haven't, as Node will only run in its own command prompt, not all command prompts.
Mocha
Mocha is deployed as a Node Packaged Module (npm) package.
Once Node has installed, open a command prompt [as an Administrator if you need to] and install the mocha node module by typing:
npm install -g mocha
The -g switch globally installs mocha so it can run from anywhere in our directory tree, not only the specific directory we ran this command from and installed it in.
Note, assertion libraries often dictate the semantic structure or "look" of our tests. If we pick a BDD-like language such as Jasmine used in chai, that will look different. I'll be working with Assert here but the principles work across the piste to other libraries such as Should JS.
Step2: A shot of mocha
Once installation completes, open a command window and run mocha straight away by typing
mocha
Note for us Windows users, the command window doesn't have to be opened in Administrator mode.
If you're new to mocha, this is both a great way to get a feel for it, and also ensures the node module installed correctly.
What does it look like? It looks like this:
Nothing exciting. This tells us there are no passing tests and it all ran in 4 ms.
What this has done is run mocha on a directory, found no unit tests (as there are none) and exited with a green "0 passing" message.
Important notes
Green for Good, Red for Bad
Notice this produces the "passing" messages in green. In mocha, green represents all passing tests and red is used for failing ones. So far, so intuitive. However, if no test are defined for a method, it won't exercise the function, will go green if everything else passed and that leaves a method untested, hence a hole in code coverage. I've covered code coverage in a previous post.
Mocha Directories
As a novice, there is one potential gotcha. By default a 'test' directory has to exist relative to where it is run from. Mocha can find tests in all sub-directories when run with the following switch from the command line.
--recursive
Do I need an assertion library... What is an assertion library?
An assertion library contains some form of result checking code to validate tests. If you've ever worked with "fluent" assertions before, this is similarly loaded through RequireJS and in Javascript, testing an assertion library is essential. Luckily, most testing frameworks come with one built in.
Step 3: Directory Structure
Now let's work through configuring our set-up to run some unit tests. The first stage is to create the directory structure. I created the following directory tree:
c:\spikes\mocha\ <-- Main subdirectory holding the classes/functions
under test.
c:\spikes\mocha\test <-- Which holds the unit-tests
For this article, let us TDD a calculator class which performs the four basic arithmetic operations of +, -, /, and *
Step 4: Creating Tests with Assert
Within the '\test' sub-directory, create a file called CalculatorTest.js and at the top of that file, put:
assert = require("assert");
Calculator = require("../Calculator.js").Calculator
When running mocha, Node follows the path to the assert library or downloads it from the default npm registry at npmjs.com if necessary. The first line states the 'assert' module is required from node. The second specifies a requirement for the calculator, which is our object under test, in the directory's parent from our folder tree. After the dot (.), I also specify the actual class we want to use as a shorthand.
Running mocha now shows our blank test file has produced the same "0 passing" message as before. So be aware that mocha will run whatever it finds in the test directory, but also will return green tests if there are no tests. So what exactly have we done if there was no change?
All we've done is reference the assert library and the location where the Calculator.js file will bemand crucially, the Calculator function within it. Not too problematic so far.
Step 5: Our first test
In the same file, copy and paste the following after the previoous couple of lines.
describe("A calculator", function () {
describe("adding 3 and 4 together", function () {
it("should return 7", function () {
var result = new Calculator().AddNumbers(3, 4);
assert.equal(7, result);
});
});
});
By default, the Assert library uses the Jasmine style of assertion. For the calculator, this basically takes the form of:
assert.equal(7, result);
Step 6: Run the test!
Save this, run it and we get:
This shows us an error! Before we go into fixing it, let's take a step back and examine what the code does.
What you are seeing
In general, we will use a describe function to document and group together the test harness and methods/scenarios it tests through those 'it(...)' function calls. The first parameter is just a string that appears in the summary view, so can be anything we like. I prefer the usual BDD 'given... when... then...' style syntax and when I first started with mocha, it was surprisingly strange to split that across the 'it' syntax so that the description on screen and the partial-fluent-like test description reads the same.
In the above snippet, I we are effectively arranging tests by arithmetic operations and have written a test for the first, simple scenario of adding 3 to 4.
Also, we can place many descriptions and many 'it' statements within the each 'describe' block. Notice that the anonymous function supplied to the 'it' arranges, acts upon and then verifies the expectation through an assertion and it is this line:
assert.equal(7, result);
That gives tests their power.
Step 7: Create the Calculator Class to Fail the test
We get a ReferenceError exception because mocha cannot find the Calculator class. That makes sense, because we have not created it yet. At this stage, that's OK, we can learn from that. Despite the dynamic nature of JavaScript, it conceptually makes sense anyway, no file, means no valid reference to the Calculator. Obvs!
Let's get started. There are many ways of writing it. Let's go with this one. Copy the following into a new file and save it as Calculator.js in the parent directory.
function Calculator() {
}
Calculator.prototype.AddNumbers = function (p1, p2) {
return 0;
}
// This exports the module so we can 'see' it from the test
// and links to the require().Calculator line in the test class
module.exports.Calculator = Calculator;
Our directory structure should now look something like:
c:\spikes\mocha\Calculator.js
c:\spikes\mocha\test\CalculatorTest.js
This basically creates our class, with a public function AddNumbers, defined to return a zero instead of the sum of two numbers, as a failing test.
Now, enter the test directory and run mocha. This time, we get something different.
Perfect! Well, almost. Mechanically it does exactly what we need it to. Before we makeit pass, the only thing left is to make the error message nice and friendly, since this doesn't really do it for me.
AssertionError: 7 == 0 // yuk! Tells me nothing
Step 8: Write Friendly Error Messages
Open the test file and change line 5 to:
assert.equal(7, result, "But the number " + result + " was returned instead");
The new, extra parameter concatenates together a string of the actual result we got and a string template, so we get a message like:
"But the number 0 was returned instead"
The test file should now look like:
describe("A calculator", function () {
describe("adding 3 and 4 together", function () {
it("should return 7", function () {
var result = new Calculator().AddNumbers(3, 4);
assert.equal(7, result, "But the number " + result + " was returned instead");
});
});
Save is and type mocha at the command prompt again. This time, the error in red is much more meaningful.
Done! Now that it can catch errors humanely, it's time to make it work!
Step 9: Making it Green
The last step in this cycle is to make the code work. Hence, it passes the verification that the tests are now designed to check for.
Open the Calculator.js class and change the AddNumbers function to be:
...
Calculator.prototype.AddNumbers = function (p1, p2) {
return (p1 + p2);
}
...
And again, go to the mocha directory and run mocha. Watch what happens this time.
GREAT! We now have our first calculator function completed. The dot is a progress bar that shows there is one test and the green text now states "1 passing".
At this point, we can check it into our version control system and hook up the test directory to the CI server of our choice, so it can run the tests by just running mocha.
Summary
Congratulations! You've TDD'ed your first class. To recap creating mocha tests:
- Download Node JS if you don't have it
- npm mocha
- Create your test class
- Create a "failing" class and run mocha against it
- Change the assertion message to make it more meaningful
- Implement the function inthe main class to make it work
- Run moch to go green & commit if it does
- Got to 4 until you've eaten all the cake
As an exercise, do the same again for the remaining calculator functions, so the process becomes habit, not forgetting the risk of dividing by zero. When you have become comfortable with that, hunt me down for some more support on other aspects of the process as well as information on other assertion libraries. Let me know if there's anything else you'd like me to cover in the comments section below.
E@Axelisys