Beautiful Assertions with Chai

So far, we’ve looked at using Mocha for doing unit testing. While using Mocha’s native assertions works fine, they can look a bit opaque the first time they are being viewed. Even with experience, seeing something like this takes some time to decipher:

assert.equal(2, addingMachine(1, 1));

While we look at this code, we might say something like this to ourselves:

Okay, so we’re saying that the things that will come up next will be equal. Okay, 2 should be equal to the result of putting one and one into the addingMachine.

That’s not so bad. But there’s a way to make our tests even easier to read. Suppose we could do this instead:

expect(2).to.equal(addingMachine(1, 1));

Now, our internal dialogue is so straight forward that I’m afraid you might be insulted that I wrote it out:

Okay, we expect 2 to equal the results of putting one and one into the addingMachine

If such gains can be made for a relatively simple assertion, think of the mental load that we save with more complex statements.

How can we do this? With a neat assertion library called Chai. Let’s talk about how to get it set up with Node:

Chai with Node: expect

  1. (If you haven’t already) Run npm init to set up your project.
  2. Run npm install mocha --save-dev to get Mocha installed. You can also use a different test runner if you prefer.
  3. Run npm install chai --save-dev to get Chai.

Now that you’re set up, let’s get the test file set up. At the top of your file, require expect like this: const expect = require('chai').expect; You can also just require Chai, and this means that you don’t need a separate require if you want to use Chai’s should. I like find that the readability gained by using the first option makes up for the inconvenience of having to require multiple things, but use what’s most readable for you.

If we’re using the same example of the addingMachine, we can now use expect to write some tests:

describe ('addingMachine', function () {
  it ('should handle negative numbers properly, returning 5 as the sum of 10 and -5', function () {
    expect(5).to.equal(addingMachine(10, -5));
  });
});

We can also use it to test error handling:

describe ('addingMachine Error Handling', function () {
  it ('should throw if passed a non-number (NaN)', function () {
    expect(addingMachine.bind(null, NaN, 5)).to.throw('passed NaN');
  });
});

Chai with Node: should

Another part of the Chai API is should. I like to use should with error handling. It reads better:

const should = require('chai').should(); // note `should` is being invoked
//snip
describe ('addingMachine Error Handling', function () {
  it ('should throw if passed a non-number (NaN)', function () {
    addingMachine.bind(null, NaN, 5).should.throw('passed NaN');
  });
});

Using Chai and Mocha in Node is great. Your tests are more readable and the setup cost of using it is low. Chai can also be run directly in the browser. For more about Chai, check out the website at http://chaijs.com. Let me know if you have any questions.

How to Use Data In Unit Tests

One of the challenges of unit testing is checking expected outputs based on certain inputs.

One possible solution to this issue is to just hard code your inputs and expected outputs into the test file. This brings some problems with it:

Why Hard-coding Data isn’t a Good Approach

  • Repeated code

    If you’re testing multiple aspects of some code, all with the same complicated input, you can end up with a mess like this:

    describe('friend contact', function () {
    it('will not throw when passed an valid data', function () {
      assert.doesNotThrow(friendContact.bind(null, [{ firstName: 'Clive', lastName: 'Lewis', phone: '44 01223 332129', lastContact: '2016-11-29', }, { firstName: 'Martin', lastName: 'Chemnitz', phone: '49 345 5520', lastContact: '2016-11-09',}]));
    });
    describe('returned output', function() {
      it('returns an object when passed valid data', function () {
        assert.equal('object', typeof friendContact([{ firstName: 'Clive', lastName: 'Lewis', phone: '44 01223 332129', lastContact: '2016-11-29', }, { firstName: 'Martin', lastName: 'Chemnitz', phone: '49 345 5520', lastContact: '2016-11-09', }]));
      });
      it('returns an object with the expected data', function () {
        assert.deepStrictEqual({ firstName: 'Martin', lastName: 'Chemnitz', phone: '49 345 5520', lastContact: '2016-11-09',}, friendContact([{ firstName: 'Clive', lastName: 'Lewis', phone: '44 01223 332129', lastContact: '2016-11-29', }, { firstName: 'Martin', lastName: 'Chemnitz', phone: '49 345 5520', lastContact: '2016-11-09', }]));
      });
    });
    });
    

    Bleh! All that repeated test data is a headache. Even with copy-paste, there’s still the potential for error and makes debugging the test hard for you and even worse for future-you (or the next poor soul that deals with it).

  • Unreadable Tests

    You might think that the readability problem in the example above could just be solved by better formatting:

    describe('friend contact', function () {
      it('will not throw when passed an valid data', function () {
        assert.doesNotThrow(friendContact.bind(null, [
          {
            firstName: 'Clive',
            lastName: 'Lewis',
            phone: '44 01223 332129',
            lastContact: '2016-11-29',
          }, {
            firstName: 'Martin',
            lastName: 'Chemnitz',
            phone: '49 345 5520',
             lastContact: '2016-11-09',
           }
         ]));
      });
      //snip, you  get the idea
    

    Now we’ve not only got the repeated code problem, we’re also left to wonder where the data stops and the test code begins. Again, this is a maintenance nightmare.

  • Difficult to refactor

    If your API provider decides to change the data structure, do you want to be the one that refactors the tests after you update your program to deal with the new data? Not only that, it’s harder to spot data input and output errors with test code and data mixed

If you hard code data into the test file, you’re in for a world of hurt. There’s a better way, let’s look at it.

How to Separate Test data

It’s not difficult to take the data out of the unit tests to make it more maintainable:

  1. Create a separate file from your test-foo.spec.js (or whatever your test file is) and call it something like test-foo-data.js. (I like to create a separate folder inside my test folder called test-data and put my data in there.)

  2. In your test file, create your data, assign it to a const and export each const so it can be used. (An alternative approach is to create and export an object with each property a data input that you’d like to run through your unit test.)

You’ll want something like this:

const longString = 'Supercalifragilisticexpialidocious';
const longStringVowelCount = 16;

module.exports = {
  longString,
  longStringVowelCount,
};

Now, you can bring it into your test file like this:

const testData = require('./test-data/test-foo-data'); // or whatever path there is to your data

And use it like this:

assert.equal(countVowelFunction(testData.longString), testData.longStringVowelCount);

Where to Get Your data

You might ask, ‘Wait a minute Nicholas, this sounds great, but where does the data come from?’

Good question. There’s a few different places you can get your data.

  • Copy data from production or the API

    This is probably the best option. If the thing you’re going to be taking the data from is already running, just copy some of the data that you’ll be using as input. This assures that the data is formatted in the same way that you’ll get when you’re up and running.

    The drawback to this approach is if the API or production is buggy, once it’s fixed, your code won’t work anymore. This isn’t that much of an issue, if your data is buggy, you can’t really call any code based on it complete.

  • Write data based on the documentation that you’ve been given

    If you’ve been given documentation for the system that you’ll be getting your data from, go ahead and model your test data on it. Unfortunately, not all documentation matches actual program behavior, so be prepared to check that your mock data matches the API.

  • Write data based on the spec that the backend is using to develop

    If the backend isn’t complete yet and you need to do parallel development for whatever reason (time crunch, team resources, or other wackiness), you can write your unit test data to be based on whatever specification the backend developers are writing to. Weather your team uses user stories or some other way of storing specifications, just base your test data on that.

  • Write data based on what makes sense to you

    If you’re the one doing the backend development, you can just write test data that seems reasonable based on what you plan to output. This has the added benefit of forcing you to consider the best way to structure your data for the front end and develop the back end accordingly.

That’s how you can use test data in your unit tests! It’s not that hard once you see how to do it. Next time, we’ll talk about another way to make your tests more readable: using Chai, a way of making assertions that more closely resembles an English sentence.

Starting Test Driven Development with Mocha

How does TDD work?

Last time in our introduction to unit testing, we touched on TDD. Let’s talk about TDD and write a simple program that illustrates the steps.

The basic principal of TDD is to write your tests first and then write code to make the tests pass. Not only that, but in TDD, you write the smallest test that you can and the smallest amount of code to make that test pass. By doing this, you won’t end up with an irrelevant test suite that takes forever to write the code for. Also, you’ll know that your tests are actually testing properly: Especially when you first start writing tests, they may not work. By making sure that they fail and then pass, you’re making it more likely that you’re actually testing what you need to.

Even though we’re writing one test at a time, you’ll want to think of the design of the program before you start writing the tests so that you’re headed in the right direction and don’t have to do a bunch of refactoring. (Refactoring is a step of TDD, but you don’t want to take all your time doing it.)

So today, let’s write a simple program that will help you stay in touch with your friends. It will take a JSON input of a data structure of your friends with their name, a way to contact them, and date that you last contacted/were contacted by them. It will then return an object with the friend’s information that you talked to the longest ago.
Let’s start with the smallest part of the requirement: your program will return an object. We’ll set up our program file and our test file and then write a simple test to assert that the program to return an object.

Let’s start with friend-contact.js in the root directory:

function friendContact(obj) {
}
module.exports = friendContact;

Then, in [rootDir]/test, create a file named friend-contact.spc.js:

const assert = require('assert');
const friendContact = require('../friend-contact');

describe('friend contact', function () {
it('returns an object', function () {
assert.equal('object', typeof friendContact());
});
});

Now, if you have mocha installed globally (npm install mocha –g), you should be able to run mocha from your root directory and it will pick up your test in the test directory. Because friendContact is an empty function right now, it should fail:

First test failing

Let’s go ahead and get this test to pass. Remember, we want to wright the minimum amount of code possible to get the test to pass, so we’ll just hard code friendContact to return an object:

function friendContact(obj) {
return {}
}
module.exports = friendContact;

Now, run the test, and it should pass:

First test passing

Now, this might seem like cheating to hard code an expected return like this. We’re not going to leave it this way and it’s important to note the benefit of doing the testing and development this way. If your tests ever start failing you won’t get much information if your one bigTestThatCoversEverythingFoo fails. However, if you’ve got many tests that cover progressive requirements of your module, you’ll have an idea of where to start debugging if something goes wrong.

At this point, it’s usual in TDD to refactor the code to make it read or look or work better, but this is really straight ahead, so we won’t do that part here.

Now, let’s write our next test. We’ve already talked about error handling with input, so let’s make sure that our input is what we expect it to be. First, we know that it should take an object:

it('will not throw when passed an object', function () {
assert.doesNotThrow(friendContact.bind(null, {}));
// see my last post
// (http://jsunittesting.com/2017/10/23/writing-unit-tests-to-handle-unexpected-inputs/)
// about why you should use bind
});

Here, the test should pass, but let’s add error handling tests that won’t pass yet:

describe('bad input error handling', function() {
it('will throw when passed a boolean', function() {
assert.throws(friendContact.bind(null, false), /only object input/, 'wrong error message');
});
it('will throw when passed null', function() {
assert.throws(friendContact.bind(null, null), /only object input/, 'wrong error message');
});
it('will throw when passed undefined', function() {
assert.throws(friendContact.bind(null, undefined), /only object input/, 'wrong error message');
});
it('will throw when passed a number', function() {
assert.throws(friendContact.bind(null, 12), /only object input/, 'wrong error message');
});
it('will throw when passed a string', function() {
assert.throws(friendContact.bind(null, 'foo'), /only object input/, 'wrong error message');
});
it('will throw when passed a symbol', function() {
assert.throws(friendContact.bind(null, Symbol()), /only object input/, 'wrong error message');
});
});

Now run your tests and they should fail. Let’s handle the cases. Remember we’re looking for the quickest way to get our tests to pass. However, if we make the function always throw, our test that asserts that it won’t throw when passed an object will fail. It’s simple to check our input:

function friendContact(obj) {
if (typeof obj !== 'object' || obj === null ) throw new Error ('only object input');
// in JavaScript typeof null === ‘object’, so we need to check that case separately with the OR
return {};
}

Now, if you run the tests, you’ll see the new input handling tests passing. However, you’ll get a new failure: because we now expect friendContact to be passed an object, our first test that passed no argument will now throw, making the test expecting the return of an object fail: (assert.equal('object', typeof friendContact());). Let’s fix it: assert.equal('object', typeof friendContact({}));

Our tests should now all pass. Let’s start actually making our features. First, we need a way to pass test data into our tests. Let’s create a folder called test-data inside the test directory and add a file called friend-data.js and add some test data:

const friendData = {
firstName: 'Clive',
lastName: 'Lewis',
phone: '44 01223 332129',
lastContact: '2017-11-29',
};
module.exports = friendData;

Now, let’s require it in our test file and use it to write a test:

const friendData = require('./test-data/friend-data');
//snip, add next test after the doesNotThrowtest
describe('returned output', function() {
it('returns an object', function () { // moved down from the top of the test
assert.equal('object', typeof friendContact({}));
});
it('returns an object with the expected data', function () {
assert.deepStrictEqual(friendData, friendContact(friendData));
});
});

Run your test, it should fail. Let’s make the test pass:

function friendContact(obj) {
if (typeof obj !== 'object' || obj === null ) throw new Error ('only object input');
return obj;
}
module.exports = friendContact;

Okay, now let’s start doing the work of making this do the lifting that we set out to do in the first place: returning one friend’s data that is the one that you contacted the longest ago. Let’s first build out friendData with more friends. We’ll have an array of objects, each with a firstName, lastName, phone and lastConatact. Build it out with at least three friends and also export an object with the data of the friend you contacted longest ago:

const friendData = [
{
firstName: 'Clive',
lastName: 'Lewis',
phone: '44 01223 332129',
lastContact: '2016-11-29',
},
{
firstName: 'Martin',
lastName: 'Chemnitz',
phone: '49 345 5520',
lastContact: '2016-11-09',
},
{
firstName: 'Dieterich',
lastName: 'Buxtehude',
phone: '49 451 397700',
lastContact: '2017-05-09',
}
];

const solution = {
firstName: 'Martin',
lastName: 'Chemnitz',
phone: '49 345 5520',
lastContact: '2016-11-09',
}

module.exports = {
friendData,
solution
};

Now that we’ve restructured the data, we’ll want to run our tests to make sure that what we’ve got still passes and we don’t need to re-write anything. (Even though it passes now, you will want to change all of the friendData to friendData.friendData to reflect what’s going on.) Surprisingly, they still pass. Now let’s re-write our returned object test asserting that we’ll get the solution if we give the function our new friendData:

it('returns an object with the expected data', function () {
assert.deepStrictEqual(friendData.solution, friendContact(friendData.friendData));
});

As we expect, our test fails. Now, let’s build our function to behave the way we want. Because we’re now expecting arrays, I re-wrote the error handler to only take arrays which required a re-write of some of our tests:

function friendContact(inputArray) {
if (!Array.isArray(inputArray)) throw new Error ('only array input');
let friendIndex;
let lastContact;
inputArray.forEach((obj, index) => {
const currentObjDate = new Date(obj.lastContact);
if (!lastContact) lastContact = currentObjDate;
if (currentObjDate <= lastContact) friendIndex = index;
});
return inputArray[friendIndex] || {};
}

module.exports = friendContact;

Our test suite ended up like this:

const assert = require('assert');
const friendContact = require('../friend-contact');
const friendData = require('./test-data/friend-data');

describe('friend contact', function () {
it('will not throw when passed an array', function () {
assert.doesNotThrow(friendContact.bind(null, []));
});
describe('returned output', function() {
it('returns an object', function () {
assert.equal('object', typeof friendContact([]));
});
it('returns an object with the expected data', function () {
assert.deepStrictEqual(friendData.solution, friendContact(friendData.friendData));
});
});
describe('bad input error handling', function() {
it('will throw when passed a boolean', function() {
assert.throws(friendContact.bind(null, false), /only array input/, 'wrong error message');
});
it('will throw when passed null', function() {
assert.throws(friendContact.bind(null, null), /only array input/, 'wrong error message');
});
it('will throw when passed undefined', function() {
assert.throws(friendContact.bind(null, undefined), /only array input/, 'wrong error message');
});
it('will throw when passed a number', function() {
assert.throws(friendContact.bind(null, 12), /only array input/, 'wrong error message');
});
it('will throw when passed a string', function() {
assert.throws(friendContact.bind(null, 'foo'), /only array input/, 'wrong error message');
});
it('will throw when passed a non-array object', function() {
assert.throws(friendContact.bind(null, {}), /only array input/, 'wrong error message');
});
it('will throw when passed a symbol', function() {
assert.throws(friendContact.bind(null, Symbol()), /only array input/, 'wrong error message');
});
});
});

That’s a good place to stop. Try extending the app some more. Continue to start with tests that fail and write code to make them pass. Here are a few ideas for how you can extend the app:

  • Verify that the friend object has the necessary data and throw if it doesn’t.
  • Decide how to and deal with situations where two friends were both contacted the longest ago on the same day.
  • Add additional contact methods (maybe email, fax), a preferred contact method and return just the preferred contact method and the name.
  • Add a way to add friends.
  • Add the ability to update when someone was contacted last.

That’s TDD. Let me know if you have questions!

Writing Unit Tests to Handle Unexpected Inputs

We’re picking up from my last post about getting started with unit testing. Now, let’s take a look at how to write tests for unexpected inputs that you might run across and how tests can document your modules.

One of the benefits of unit testing is that it forces you to be clear about what your module is doing and what outcomes you expect for different inputs. Let’s bring those benefits to our adding-machine.js file. Here’s where it should be right now:

function addingMachine (number1, number2) {
  return (number1 + number2);
}
module.exports =  addingMachine;

The problem here is that we don’t know what to expect if there’s input that you wouldn’t expect. What should our adding machine do if it’s given a Boolean, a symbol or an object as input? We could see what JavaScript would give us and then test for that, but that’s lazy: it doesn’t force us to consider what a user of the module would want and write the program appropriately. In this case, I want my adding machine to only take number types and also throw an error if passed NaN (not a number, which is technically a number if you look at it with typeof). So, let’s write a test that will test to see if adding machine will throw an error if passed NaN:

  describe ('addingMachine Error Handling', function () {
    it ('should throw if passed a non-number (NaN)', function () {
      assert.throws(addingMachine.bind(null, NaN, 5), Error, /passed NaN/, 'NaN input, wrong error message');
  });
});

Let’s talk about what we’re doing here. The assert.throws function expects a function as input. Because of this, we can’t put addingMachine(NaN, 5) in as an argument, it just returns its output. If it throws while it’s running, it won’t register for your test. That’s why we need to use bind. bind basically creates a new function with its arguments hard-coded to the inputs given with the this context passed as the first argument. In other words, addingMachine.bind(null, NaN, 5) creates a function that has number1 hard-coded to NaN and number2 set to 5. We don’t have to worry about the this context because we’re not using this in the function. We can just set it to null. The second argument, /passed NaN/ is regex for the error that it expects. The third input, 'NaN input, wrong error message' is the error that will be given if it’s the wrong error message.

Let’s run the test. With npm test, you’ll see that it fails. That’s because we haven’t re-written addingMachine yet. Let’s do that:

function addingMachine (number1, number2) {
  if (Number.isNaN(number1) || Number.isNaN(number2)) throw new Error ('passed NaN');
  return (number1 + number2);
}
module.exports =  addingMachine;

You don’t need to be as verbose as I in your error handling, but you get the idea. Now, if you re-run your test with npm test, it will pass.
As you probably know, there are 7 different data types in JavaScript:
– Boolean
– Null
– Undefined
– Number
– String
– Symbol
– Object
There’s only one of those types that we want addingMachine to deal with: Number. Let’s put in tests so that we will expect an error if one of the other types is thrown:

    it ('should throw if passed a string', function () {
      assert.throws(addingMachine.bind(null, '5', 5), /passed non-number/, 'wrong error message');
    });
    it ('should throw if passed undefined', function () {
      assert.throws(addingMachine.bind(null, 5, undefined), /passed non-number/, 'wrong error message');
    });
    it ('should throw if passed null', function () {
      assert.throws(addingMachine.bind(null, null, 5), /passed non-number/, 'wrong error message');
    });
    it ('should throw if passed an object', function () {
      assert.throws(addingMachine.bind(null, {}, 5), /passed non-number/, 'wrong error message');
    });
    it ('should throw if passed a boolean', function () {
      assert.throws(addingMachine.bind(null, true, 5), /passed non-number/, 'wrong error message');
    });
    it ('should throw if passed a symbol', function () {
      const mySymbol = Symbol();
      assert.throws(addingMachine.bind(null, mySymbol, 5), /passed non-number/, 'wrong error message');
    });

Run your npm test. It should fail because you haven’t written the throw for those issues.

Let’s update addingMachine to throw whenever we get one of the other types:

function addingMachine (number1, number2) {
  if (Number.isNaN(number1) || Number.isNaN(number2)) throw new Error ('passed NaN');
  if (typeof number1 !== 'number' || typeof number2 !== 'number') throw new Error ('passed non-number');
  return (number1 + number2);
}
module.exports =  addingMachine;

If you run your tests now, they should all pass.

I encourage you to test all different types of inputs in all the tests you write as well as unique inputs of the types you expect. Your tests become a self-documenting way of showing future maintainers of your code how your program is expected to work. It will also clarify for you what your code is doing and put you in a place where you’re more familiar with what’s going on and why.

One thing you might have noticed this time is that our way of writing code might have seemed backwards this time: we wrote tests and then we made them pass. This is a technique called “Test Driven Development” or TDD. It’s a way of developing new code that puts an emphasis on writing tests for what you want a small amount of code to do and then writing only just enough to get it to pass. It offers other benefits as well. We’ll explore TDD next time. In the meantime, here are the final states that your two files should have ended up in after these two introductory posts on unit testing:
adding-machine.js:

function addingMachine (number1, number2) {
  if (Number.isNaN(number1) || Number.isNaN(number2)) throw new Error ('passed NaN');
  if (typeof number1 !== 'number' || typeof number2 !== 'number') throw new Error ('passed non-number');
  return (number1 + number2);
}
module.exports =  addingMachine;

test.js:

const assert = require('assert');
const addingMachine = require('./adding-machine');

describe ('addingMachine', function () {
  it ('should return 2 as the sum of 1 and 1', function () {
      assert.equal(2, addingMachine(1, 1));
  });
  it('should return 11 as the sum of 3 and 8', function () {
    assert.equal(11, addingMachine(3, 8));
  });
  it ('should handle zero properly, returning 1 as the sum of 0 and 1', function () {
      assert.equal(1, addingMachine(0, 1));
  });
  it ('should handle negative numbers properly, returning 5 as the sum of 10 and -5', function () {
      assert.equal(5, addingMachine(10, -5));
  });
  describe ('addingMachine Error Handling', function () {
    it ('should throw if passed a non-number (NaN)', function () {
      assert.throws(addingMachine.bind(null, NaN, 5), /passed NaN/, 'NaN input, wrong error message');
    });
    it ('should throw if passed a string', function () {
      assert.throws(addingMachine.bind(null, '5', 5), /passed non-number/, 'wrong error message');
    });
    it ('should throw if passed undefined', function () {
      assert.throws(addingMachine.bind(null, 5, undefined), /passed non-number/, 'wrong error message');
    });
    it ('should throw if passed null', function () {
      assert.throws(addingMachine.bind(null, null, 5), /passed non-number/, 'wrong error message');
    });
    it ('should throw if passed an object', function () {
      assert.throws(addingMachine.bind(null, {}, 5), /passed non-number/, 'wrong error message');
    });
    it ('should throw if passed a boolean', function () {
      assert.throws(addingMachine.bind(null, true, 5), /passed non-number/, 'wrong error message');
    });
    it ('should throw if passed a symbol', function () {
      const mySymbol = Symbol();
      assert.throws(addingMachine.bind(null, mySymbol, 5), /passed non-number/, 'wrong error message');
    });
  });
});

Getting started with Unit Testing

There are lots of different ways to get started with unit testing in JavaScript. Today, we’ll look at a simple way to get started: Mocha, a popular unit testing engine. We’ll just look at the vanilla version of Mocha (I suppose the black version would be a more appropriate metaphor in this case). You can make your test cases easier to read by making them read more like an English sentence with packages like Chai, but we’ll leave that for another day.

Prerequisites for this tutorial:
– Node is installed on your machine (how to install node)
– NPM is installed on your machine (should come with Node)

Go ahead and create a directory in your root directory (C:\ or ~) or your code directory named unit-testing. Create a file called test.js Do an npm install mocha there. Then npm init, putting in whatever values you like for the name. Make sure that the test command is mocha

Now, run npm test. You should see something like this:

Tests passing because there are no tests!

Okay, it’s passing, but that’s because we don’t have any tests. Let’s fix that. We’ll create a simple adding machine that will tell us the sum of two numbers. A trivial task, but we’re here to learn unit testing, not show off our amazing JS skills. Create a file named adding-machine.js and put in this code:

function addingMachine (number1, number2) {
  return (number1 + number2);
}
// We need to export this so our test file can see it, so add this also:
module.exports =  addingMachine;

Now, switch over to your test.js file and let’s add some test. First, you’ll want to add the assert library, which is how you’ll tell mocha what to expect: const assert = require('assert');. Then, you’ll require the program you’re testing: const addingMachine = require('./adding-machine');. Now, let’s actually add some tests.

We’ll start with a describe, which is a way to group your tests:

describe('addingMachine', function() {
  // your tests will go here
});

This is so that your tests neatly fall under the describe and when reading output you can see that those tests are describing the adding machine. It might not seem that important, but if you’re testing a whole bunch of modules at the same time (say, in your build system), having a well-structured test suite is a great benefit. Okay, now let’s actually write a test:

it('should return 2 as the sum of 1 and 1', function () {
  assert.equal(2, addingMachine(1, 1));
});

When we use equal, we’re telling mocha to expect that two inputs of the equal function should be equal. (Wow, I’m sure you’d never have guessed!) So, in this case, we’re saying that the results of putting 1 and 1 into the adding machine should equal the first argument, 2.
Now run npm test and you should see this:

Sum 1 and 1 test passing

Great! The test passed. If yours didn’t pass, check to make sure that your code matches mine. You can also use console.log in both the test.js file and the adding-machine.js file to check your inputs and arguments.

This is a fine test case, but it’s not enough. If the code for the adding machine had been return (number1 + number1), this test still would have passed, so it’s important that we check inputs that are different. Let’s do it!

it('should return 11 as the sum of 3 and 8', function () {
  assert.equal(11, addingMachine(3, 8));
});

Run npm test again, it should pass. We should also check numbers that have special properties to make sure that addingMachine handles them the way we expect:

  it ('should handle zero properly, returning 1 as the sum of 0 and 1', function () {
      assert.equal(1, addingMachine(0, 1));
  });
  it ('should handle negative numbers properly, returning 5 as the sum of 10 and -5', function () {
      assert.equal(5, addingMachine(10, -5));
  });

Now, we’ve got a good test suite going. Here’s where your files should be at this point:
test.js:

const assert = require('assert');
const addingMachine = require('./adding-machine');

describe ('addingMachine', function () {
  it ('should return 2 as the sum of 1 and 1', function () {
      assert.equal(2, addingMachine(1, 1));
  });
  it('should return 11 as the sum of 3 and 8', function () {
    assert.equal(11, addingMachine(3, 8));
  });
  it ('should handle zero properly, returning 1 as the sum of 0 and 1', function () {
      assert.equal(1, addingMachine(0, 1));
  });
  it ('should handle negative numbers properly, returning 5 as the sum of 10 and -5', function () {
      assert.equal(5, addingMachine(10, -5));
  });
});

adding-machine.js:

function addingMachine (number1, number2) {
  return (number1 + number2);
}

Next time, we’ll take a look at how to expand our test suite and handle unexpected inputs.

Why Unit test in JavaScript?

Recently at my work there has been a push to do more unit tests on the front end. There’s also been a lot of pushback: “Why do we have to unit test?” “This just takes more of our time that we could be using for feature work,” “Our automated UI tests will catch problems,” “Our QA testers are great, they’ll catch any bugs,” and so on.

Most of these arguments seem like compelling reasons to not unit test. Isn’t that just something that Java programmers do? Actually, unit testing in your JavaScript can save you time and headaches. Not only that, but you can look like a hero to your coworkers by reducing the number of test cases that they have to cover, either with manual tests or automated UI tests.

Not only that, but you’ll be a hero for whoever has to refactor or add things to the code that you’ve covered with good unit tests. There are few things more terrifying than having to go into old, crusty code to refactor it for performance or add features to it if there are no unit tests. If you break something, how will you know? Who knows how many downstream things depend on this code and its obtuse output? Will your testers (if you have any) catch things or will they not have time to do as comprehensive a regression check as this needs? Or will your customers come screaming complaining of a broken website, or worse, will they disappear without a peep?

You’ll also have a body of work that you can thank yourself for later: there are few better feelings than having confidence from good unit tests that you can refactor the code until it’s a beautiful shining example of clean code that would make a seasoned developer weep with joy. Wait, it still works, right? Well, the good unit tests pass, so we’re good to go.

Well-written unit tests can produce results that seem impossible for such a simple thing. It gives confidence to you that your code is working at a basic level. It gives confidence to other developers on your team that you’re looking out for their sanity and that you’re likely writing maintainable code that isn’t adding a ton of technical debt to the project. It gives confidence to QA that you’ve covered the basic scenarios and handled edge cases so that they don’t have to. It also checks a box that many in management are looking for and improves code coverage metrics.

There’s a huge number of reasons to unit test, but how do you get started? We’ll look at that next time.