A crash course on testing with Node.js

Adnan Rahić - Sep 23 '17 - - Dev Community

JavaScript is a beautiful language. You must believe I’m crazy. Maybe you’re crazy for agreeing with me. But why would I say something like this? As a language, JavaScript gives you no support whatsoever. It bites your head off if you give it the slightest chance, and it has bizarre error messages if left unhandled. So you tell me, why is it beautiful?

Because it creates good, responsible, and intelligent developers. By worrying about getting your head ripped off by the smallest mistake, you adapt and overcome. The skill gained has less in common with programming and much more with a programmer’s state of mind. Because you are getting used to not having an invisible force guide you through the code. Instead, you rely on yourself, and your own skill. Hence, me stating something as crazy as I did above.

Why then, does this create good programmers? A good programmer is responsible, meticulous and reliable. Programmers like these make sure their code works. No matter which environment, or which machine. These masters of their craft, always cover code with tests, to ensure their work is valid. They have my utmost respect. I would like to believe they have your as well.

Baby steps.

To lay the foundation of what a basic test case would look like let’s create a simple function.

function addTwoNumbers(x, y) {
  return x + y;
}
console.log(addTwoNumbers(5, 1));
Enter fullscreen mode Exit fullscreen mode

Calling this function we can see the result is 6. Because we know basic math, it makes perfect sense. But what if the function is really complex?

Let’s make sure to write a test case to ensure the function is valid no matter what.

function addTwoNumbers(x, y) {
  return x + y;
}

function testAddTwoNumbers() {
  var x = 5;
  var y = 1;
  var sum1 = x + y;
  var sum2 = addTwoNumbers(x, y);

  console.log('addTwoNumbers() should return the sum of its two parameters.');
  console.log('Expect ' + sum1 + ' to equal ' + sum2 + '.');

  if ( sum1 === sum2 ) 
    return console.log('Passed.');

  console.log('Failed.');
}

testAddTwoNumbers();
Enter fullscreen mode Exit fullscreen mode

See this? We’re defining the values to add, and creating their sum. Then we call addTwoNumbers() assigning it to another variable. Having done this, we’re ready to test the equality. What’re we expecting? Well, sum1 should be equal to sum2, if the function we created works as expected. Running this piece of code you should see the following get logged to the command line:

addTwoNumbers() should return the sum of its two parameters.
Expect 6 to equal 6.
Passed.
Enter fullscreen mode Exit fullscreen mode

Congratulations, you’ve written your first unit test! The act of unit testing lies in writing tests for small units of code. Hence the name. Meaning you’ll write individual test cases for validating the behavior of functions, methods, and objects. Exactly like we did above.

What if we add a deliberate bug to our code? For the heck of checking if the unit test will fail gracefully. Change the addTwoNumbers() function to:

function addTwoNumbers(x, y) {
  return x + x; // deliberate bug!
}
Enter fullscreen mode Exit fullscreen mode

Run the unit test once again and you’ll see it fail like it should.

addTwoNumbers() should return the sum of its two parameters.
Expect 6 to equal 10.
Failed.
Enter fullscreen mode Exit fullscreen mode

A bit of theory.

A unit test consists of three parts.

  1. Arrange
  2. Act
  3. Assert

From their names alone it is easy to comprehend what they stand for. Let’s break it down while looking at some code.

function addTwoNumbers(x, y) {
  return x + y;
}

function testAddTwoNumbers() {

  // 1. ARRANGE
  var x = 5;
  var y = 1;
  var sum1 = x + y;

  // 2. ACT
  var sum2 = addTwoNumbers(x, y);

  console.log('addTwoNumbers() should return the sum of its two parameters.');
  console.log('Expect ' + sum1 + ' to equal ' + sum2 + '.');


  // 3. ASSERT
  if ( sum1 === sum2 ) 
    return console.log('Passed.');

  console.log('Failed.');
}

testAddTwoNumbers();
Enter fullscreen mode Exit fullscreen mode

In the first part we arrange all necessary preconditions and inputs. You can see we defined the variables to add and the sum of these variables. The second step is to act on the function, object or method under test. Lastly, we assert that the expected results have occurred.

You may find the word assert a bit overwhelming. As a non-native English speaker, I sure did, when I first heard it. Not to worry, it only means to claim. You are asserting a truth, meaning you claim something is true. As simple as that.

Assertions are a tool to do basic sanity checking for programmer errors.
— Marijn Haverbeke, Eloquent JavaScript

Want to write your own assertion? Sure you do. Check this out.

var assert = {
  equal: function(firstValue, secondValue) {
    if (firstValue != secondValue) 
      throw new Error('Assert failed, ' + firstValue + ' is not equal to ' + secondValue + '.');
  }
};

function addTwoNumbers(x, y) {
  return x + y;
}

function testAddTwoNumbers() {

  // 1. ARRANGE
  var x = 5;
  var y = 1;
  var sum1 = x + y;

  // 2. ACT
  var sum2 = addTwoNumbers(x, y);

  console.log('addTwoNumbers() should return the sum of its two parameters.');
  console.log('Expect ' + sum1 + ' to equal ' + sum2 + '.');


  // 3. ASSERT
  try {

    assert.equal(sum1, sum2);

    console.log('Passed.');
  } catch (error) {
    console.log(error.message);
  }

}

testAddTwoNumbers();
Enter fullscreen mode Exit fullscreen mode

On line 1 we instantiate a new object named assert, immediately adding a method called equal. If the two passed parameters are not equal, the function will throw an error. That’s it, that’s all the logic in the whole method. Now, on line 27 we’re wrapping the assert stage in a try catch block, and calling the assert.equal() method. Only if the values are not equal will an error be thrown and caught in the catch block. Otherwise, the thread of execution will continue and log 'Passed.' to the console. Go ahead and try it out!

How about we get serious?

The examples above have shown the fundamentals of testing in general. Also pointing out the required mindset needed to succeed in the field of programming. It’s time to bring out the big guns. You’ll rarely ever use the code above in a production environment. Nevertheless, it’s crucial in the understanding of what is to come.

You can use many various tools to write tests for Node.js applications in production. An example is the built-in assertion library. Yes, Node does have assertions baked in. Only change line 1.

var assert = require('assert');

function addTwoNumbers(x, y) {
  return x + x;
}

function testAddTwoNumbers() {
  var x = 5;
  var y = 1;
  var sum1 = x + y;
  var sum2 = addTwoNumbers(x, y);

  console.log('addTwoNumbers() should return the sum of its two parameters.');
  console.log('Expect ' + sum1 + ' to equal ' + sum2 + '.');

  try {

    assert.equal(sum1, sum2);

    console.log('Passed.');
  } catch (error) {
    console.error('Failed.');
  }
}

testAddTwoNumbers();
Enter fullscreen mode Exit fullscreen mode

By changing out our custom assert object for the built-in Node module our code works exactly the same. The default assertions in Node are extremely powerful, you can take a longer look at them here.

However, tools like Mocha and Chai are the bread and butter of testing Node.js applications.

Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test — mochajs.org

Chai is a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework. — chaijs.com

Let’s check this out. First of all, you’ll need to init a new Node project by hooking it up to npm.

Open up a terminal window in your directory of choice, and run:

npm init
Enter fullscreen mode Exit fullscreen mode

Please feel free to enter through all of the prompts. When you’ve done that, you’ll need to install the required modules.

npm install --save-dev mocha chai
Enter fullscreen mode Exit fullscreen mode

Now you can open up your code editor of choice and start by adding files like so:

> test
  - test.js
- addTwoNumbers.js
Enter fullscreen mode Exit fullscreen mode

One test directory with a test.js file, and another file named addTwoNumbers.js in the root of the directory. Go ahead and paste the addTwoNumbers function into the addTwoNumbers.js file like so:

function addTwoNumbers(x, y) {
  return x + y;
}
module.exports = addTwoNumbers;
Enter fullscreen mode Exit fullscreen mode

Don’t forget to export it to be able to require it later on. Now we can start with the fun part. Open up test.js and start by laying the foundation for our tests.

var expect = require('chai').expect;
var addTwoNumbers = require('../addTwoNumbers');

describe('addTwoNumbers()', function () {
  it('should add two numbers', function () {

    // 1. ARRANGE
    var x = 5;
    var y = 1;
    var sum1 = x + y;

    // 2. ACT
    var sum2 = addTwoNumbers(x, y);

    // 3. ASSERT
    expect(sum2).to.be.equal(sum1);

  });
});
Enter fullscreen mode Exit fullscreen mode

At the beginning of the file we need to require both Chai and addTwoNumbers. Look at the way we required Chai, only grabbing expect. Chai comes with three types of interfaces for creating assertions. They are all valid. Which one you choose is only preference. I feel like expect suits me just fine. Don’t get mindblown by the test syntax. It’s created to simulate natural human speech patterns. The describe block creates a test environment. The it blocks defines test cases which need to pass. Reading it out loud sounds rather fine. Describe addTwoNumbers(), it should add two numbers. Makes perfect sense! Can you now see why testing is important apart from making sure the code works? A test is in itself documentation. Writing a test will explain what the code does. Every other developer working on the code base will have no issue understanding it in no time.

All that’s left is to run the tests. Add "test": "mocha" in the scripts section of your package.json and you’ll be ready to go!

Hint , your package.json should look like this:

{
  "name": "testing",
  "version": "1.0.0",
  "description": "",
  "main": "test.js",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "test": "mocha"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "chai": "^4.1.1",
    "mocha": "^3.5.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Jump back to your terminal window and run npm test. You’ll see an awesome interface with some green text saying there is 1 passing test!

Taking it all in.

You’ve now experienced the natural process of covering code with tests. All the examples have been showing unit tests, which is more than enough to begin with. When you get comfortable with these concepts, understanding integration and end-to-end testing will be like a walk in the park. But that’s a topic for another article.

I urge you to continue playing with these testing tools. Try to include them into your existing development process. You will see an overall improvement in code quality and mental health. Trust me, having peace of mind with a completely green test suite does wonders for the nerves.

If you want to take a look at all the code we wrote above, here’s the repository. Or if you want to read my latest articles, head over here.

Latest stories written by Adnan Rahić - Dev.to()

Hope you guys and girls enjoyed reading this as much as I enjoyed writing it. Do you think this tutorial will be of help to someone? Do not hesitate to share. If you liked it, click the cute unicorn below.


Buy Me A Coffee

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player