Post

Testing for the Novice JavaScript Developer

“If builders built buildings the way programmers wrote programs, then the first woodpecker that came along would destroy civilization.” ~Gerald Weinberg

I love testing. Quite early in my journey of learning to code, I heard of this “Test Driven Development” thing. Around the same time I was starting to expand the scope of my projects, and was running into very tricky bugs. Sometimes things that worked stopped working and I had no idea why. Testing only revealed more problems; my code was a giant tangled mess that couldn’t be tested. Testing my code has fundamentally improved my coding, and I hope this introduction helps you too.

What I hope you’ll gain from this post:

I’ll expect a very basic understanding of command line and npm.

Setup

First let’s make a new project. Create a folder and enter into the command line:

npm init -y

This will initialise the project with npm.

Then install mocha.

npm install mocha -g

or

sudo npm install mocha -g

And install chai with:

npm install chai --save-dev

Mocha is the framework that is used to set up the tests, run the tests, name the tests and do your tests housekeeping. Chai is an assertion library, meaning it’s the thing that you’ll use to check if things are doing what you expect. This will become more clear with the examples that follow.

In your root directory create a file called index.js. This will be the file we’ll test. Also create a folder called test and put a file inside called index.spec.js. This file is where we will write the tests for index.js.

Your project should look like so:

├── yourAwesomeProject/
│   ├── index.js
│   ├── test
│   │   ├── index.spec.js
│   ├── package.json

Alright! We’ve done nothing, but now is a perfect time to make sure mocha is working.

Type

mocha test

into your command prompt.

Hopefully you get something like this ->

0 passing (3ms)

You just ran the test runner! There are no tests there, but that doesn’t matter. We’ll fill it with tests soon enough.

Writing your first test!

Write the following code into your index.js.

// index.js

// Stub
exports.addTwoNumbers = function (a, b) {
    return 0;
}

This function is called a “stub” function. This code doesn’t have any logic, but it still returns a number. This allows us to write a test for it without the hassle of trying to implement the function logic yet.

Jump over to your index.spec.js file. I’ll show you the code, and then I’ll break it down. Make sure to type it out yourself.

// index.spec.js

var index = require("../index");
var chai = require("chai");
var expect = chai.expect;
describe("index.js tests", function() {
    it("addTwoNumbers returns a number", function() {
        expect(index.addTwoNumbers(0, 0)).to.be.a("number");
    });
});

To make sure everything is working, once again type mocha test into your terminal. You should get this cool snazzy output in your terminal:

index.js tests
    ✓ addTwoNumbers returns a number
1 passing (11ms)

Everything is working, so let’s understand what we’ve written.

The first two lines import your file index.js which contains the function you want to test and chai which is the assertion library you previously installed using npm. var expect = chai.expect; just gives us a short way to call chai.expect.

describe() — Grouping a collection of individual tests

describe(<Heading of Tests>,testsInCallback);

Describe is like a heading. It’s a way to group a collection of tests together. You’ll notice in the above example, ‘index.js tests’ is the heading, and ‘addTwoNumbers returns a number’ is the individual test (it) grouped with it.

it() - ‘Individual Test’

it(<Name of Test>, testInCallback);

Every test assertion needs its own individual test. For example, to test that our function ‘addTwoNumbers’ can actually add two numbers, we should add at least one other test.

We’re going to add another test:

// index.spec.js

var index = require("../index");
var chai = require("chai");
var expect = chai.expect;
describe("index.js tests", function() {
    it("addTwoNumbers returns a number", function() {
        expect(index.addTwoNumbers(0, 0)).to.be.a("number");
    });
    it("addTwoNumbers can add 1 + 2", function() {
        expect(index.addTwoNumbers(1,2)).to.equal(3);
    });
});

We’ve added it to the group “index.js tests” demarcated by the describe function, and we’ve added it below the previous individual test. You’ll notice that the syntax used by expect is really easy to read.

expect(<your function>).to.equal(<something>);

The above code is how you check equality. If you are checking deep equality, for example to check if the fields inside objects are equal you can just add ‘deep’ to the chain like so:

expect(<yourObject>).to.deep.equal(<anotherObject>);

The documentation for chai’s expect is clear and straight forward so I recommend you take a look.

If you run mocha test now you’ll get a test failure.

index.js tests
    ✓ addTwoNumbers returns a number
    1) addTwoNumbers can add 1 + 2
1 passing (19ms)
  1 failing
1) index.js tests addTwoNumbers can add 1 + 2:
AssertionError: expected 0 to equal 3
      + expected - actual
      -0
      +3
at Context.<anonymous> (test/index.spec.js:9:45)

This information is extremely valuable as it allows you to see what your expect actually returned (in this case a zero).

Now that your test is failing, you can resolve it by fixing the code in index.js. Tests will not make your code perfect, but they do provide peace of mind.

As a quick note, this technique is called “Red, Green, Refactor”. We’ve only covered the “Red, Green” part, but the idea is that you write a test that fails. Then fix your code to pass the test. Finally you can refactor your code with the knowledge that you’ve got a test to check that your refactor doesn’t break anything.

We’ve just spent the whole time so far with synchronous tests. But what if you need to test callbacks or promises?

Asynchronous tests

Testing asynchronous code is not difficult, but requires one additional piece of code that hasn’t been covered yet called done(). Without done, mocha will dispatch your asynchronous code and continue without waiting for the assertion or execution of your callback. I’ll show you with a concrete example.

describe("Broken Async Code", function() {
    it('Async test', function() {
        setTimeout(function(){
            //failing test
            expect(true).to.be.false;
        }, 1000);
    });
});

The above test passes. This is because it only fails a test if it catches the error from the expect. Because this is asynchronously executed (by a delay of 1 second), mocha skips over this as a passing test. We need to tell Mocha to wait by adding done. done must be added as an argument and after the assertion (to tell Mocha that we’ve run the test).

describe("Fixed Async Code", function() {
    it('Async test', function(done) {     // Done added here.
        setTimeout(function(){
            //failing test
            expect(true).to.be.false;
            done();     // Tells mocha to run next test.
        }, 1000);
    });
});

You’ll notice that when you run mocha test, it’ll pause for 1 second and then give a failure. Sadly this isn’t the really detailed error that mocha gave earlier in the synchronous examples. This is solved by mocha’s built-in promises, but this is another topic and out of scope for this post. If you’re interested I recommend looking at this (it’s a really nice way of writing async tests with detailed responses).

Some super useful features I use

  1. To run a single test or group just add .only to the end of describe or it.
describe.only("index.js tests", function () {
it.only("addTwoNumbers returns a number", function () {
  1. Functions that can be used inside describe blocks.

    • before(), beforeEach(), after(), afterEach()
    • Use for set up and clean up.
  2. Mocha can be used in the browser! It loads up a cool web page with a todo list style format with all your tests. (This makes testing web pages far easier).

Good luck. And remember that testing is a skill, so practice will make perfect. :)