Introduction to Unit Testing es6 JavaScript Modules

Introduction to Unit Testing es6 JavaScript Modules

In this post I will demonstrate how to go about unit testing the most basic example of JavaScript code. You’ll quickly see how important unit testing can be and how much it can introduce a very solid quality check against your code, allowing you to release more confidently.

To start, let’s assume the following very basic example. Note that this example is so basic you’d probably never have code like this.

string-functions.js

export function convertToLowerCase(str) {
     return str.toLowerCase();
}

This exported function, when called, will take in a string and return the string in all lowercase. Now let’s consider writing some unit tests for the code as-is. We can check to see the function actually exists. This is the most basic check and while it doesn’t provide any actual code coverage it will add a quality check to make sure if the function is removed the test will not pass.

For all of these examples we’re assuming that we’re running karma, mocha, sinon, and chai for our technology stack (although other packages will work, too). We’re also going to assume that these are wrapped in the required describe wrapper, so this is just the it statement for each test.

it('convertToLowerCase should be a function', function() {
     assert.isFunction(strFunctions.convertToLowerCase);
});

As I previously stated this provides no coverage and we actually have 0% code coverage at this point. But before we can write more tests we probably need to ask some questions about the code in order to know what tests to write. So let’s ask some questions about this code:

  1. Is passing a string required?
  2. What happens if something other than a string is passed?
  3. Is a string always returned?
  4. Does the function return a lowercase version of the string that was passed?

Let’s look at these one at a time.

Is passing a string required?

Yes, in order to use the toLowerCase() method a string needs to be passed.

What happens if something other than a string is passed?

Passing something like an object is going to throw an error. Passing nothing will cause an error. We really only want to be able to pass a string to this function.

Is a string always returned?

For sure if a string is passed in a string will be passed out. But if we’re not passing a string in we have unexpected results. We always need a string.

Does the function return a lowercase version of the string that was passed?

We need to assume a string is being passed, it’s important to make sure the expected result is the actual result of this function so that we can possibly iterate on it later.

Based on these answers we need to modify the code some in order to ensure expected results always.

export function convertToLowerCase(str) {
     if (!str || typeof str !== 'string') {
          return false;
     }

     return str.toLowerCase();
}

Now, with these changes, we force the developer to pass in a string. We won’t get any sorts of errors if we don’t because we aren’t executing that code. We can also then guarantee that we’re always going to return a string and the string will always be as expected. So let’s write tests to confirm this:

it('convertToLowerCase() should return bool false when no parameter is passed', function() {
     assert.isBoolean(strFunctions.convertToLowerCase());
     assert.equal(false, strFunctions.convertToLowerCase());
});
it('convertToLowerCase() should return bool false when parameter passed is not a string', function() {
     assert.isBoolean(strFunctions.convertToLowerCase(1234));
     assert.equal(false, strFunctions.convertToLowerCase(1234));

     assert.isBoolean(strFunctions.convertToLowerCase(['asdf']));
     assert.equal(false, strFunctions.convertToLowerCase(['asdf']));

     assert.isBoolean(strFunctions.convertToLowerCase(true));
     assert.equal(false, strFunctions.convertToLowerCase(true));

     assert.isBoolean(strFunctions.convertToLowerCase(null));
     assert.equal(false, strFunctions.convertToLowerCase(null));

     assert.isBoolean(strFunctions.convertToLowerCase({ 'option': true }));
     assert.equal(false, strFunctions.convertToLowerCase({ 'option': true }));
});
it('convertToLowerCase() should return a string when a string is passed', function() {
     assert.isString(strFunctions.convertToLowerCase('ASDF'));
});
it('convertToLowerCase() should return a lowercase string when a string is passed', function() {
     assert.equal('asdf', strFunctions.convertToLowerCase('ASDF'));
     assert.equal('asdf', strFunctions.convertToLowerCase('asdf'));
     assert.equal('another test', strFunctions.convertToLowerCase('Another Test'));
});

Before we refactored the code we had no branches but 1 line, 1 statement, and 1 function, all for 0% code coverage. When we refactored the code we introduced 4 branches and an additional line and statement. We’ve successfully tested all lines, statements, functions, and branches. We’ve introduced negative checks to go along with our happy-path tests and have much greater confidence in the future that if we make a change to this function we’ll know before we release it if it might break a function that relies upon it.

Pin It
Read More Leave comment

Code Coverage with es6, Babel, Karma, Mocha, and Webpack

I was trying to get JavaScript unit testing set up at work and was really struggling. While I could get unit tests and established a testing framework I wasn’t able to determine my code coverage. For those of you that haven’t tested before, code coverage is a really important aspect of unit tests because it will tell you where you have gaps in your tests. If you’re not testing a specific branch or function you have a greater chance of pushing out a bug because the code isn’t tested.  So code coverage is pretty important.

Take this piece of my devDependencies within my package.json.

package.json:

"devDependencies": { 
    "babel-core": "6.23.1", 
    "babel-loader": "6.3.2", 
    "babel-preset-es2015": "6.22.0", 
    "babel-preset-stage-1": "6.22.0", 
    "chai": "3.5.0", 
    "gulp-mocha": "4.0.1", 
    "karma": "1.5.0", 
    "karma-cli": "1.0.1", 
    "karma-coverage": "1.1.1", 
    "karma-mocha": "1.3.0", 
    "karma-phantomjs-launcher": "1.0.2", 
    "karma-sinon-chai": "1.2.4", 
    "karma-webpack": "2.0.2", 
    "mocha": "3.2.0", 
    "sinon": "1.17.7", 
    "sinon-chai": "2.8.0", 
    "webpack": "2.2.1" 
} 

This is a pretty standard build out when you’re using es6 (Babel) and are transpiling down from 6 to 5 as part of your bundling/minifying/uglifying process (Webpack). I chose the Karma/Mocha/Sinon/Chai technology stack simply because it’s widely adopted and lightweight. Some people prefer Jasmine more, which is fine, Jasmine comes with a lot of this functionality built-in, but I like to have options.

A lot of sites tell you to set up your karma.conf.js file something like this:

karma.conf,js


const src = './scripts/**/*.js',
      tests = './tests/**/*.spec.js';

const karmaConfig = {
    frameworks: ['mocha', 'sinon-chai'],
    files: [
        src,
        tests
    ],
    preprocessors: {
        src: ['webpack', 'coverage'],
        tests: ['webpack']
    },
    webpack: webpackConfig('test'),
    reporters: ['coverage'],
    browsers: ['PhantomJS'],
    client: {
        captureConsole: false
    },
    specReporter: {
        showSpecTiming: true
    },
    reportSlowerThan: 25,
    coverageReporter: {
        dir: 'coverage',
        reporters: [
            { type: 'text' },
            { type: 'text-summary' },
            { type: 'html' }
        ]
    }
};

karmaConfig.preprocessors[src] = ['webpack', 'coverage'];
karmaConfig.preprocessors[tests] = ['webpack'];

module.exports = function (config) {
    config.set(karmaConfig);
}

If you have something like this you’ll get bundled output and your tests will run, but your test coverage will be extremely low. If you add the coverage reporter to the tests you’ll likely get an error saying you can’t require the module because the module is already required. You’ve created a cyclical reference.

What we want really is to run the tests, and from the tests determine what source code was executed by the tests. But we also need to run the tests and code coverage on the non-transpiled code. In order to do this we need to include another handy babel package:

npm install babel-plugin-istanbul --save-dev

Istanbul is pretty much the gold standard when it comes to coverage reporting in the front-end world and it does this extremely well. I also have a JSON file of config settings, that currently has this node for Babel:

config.json


"babel": {
     "presets": [
          ["es2015", {
               "modules": false
          }],
          "stage-1"
     ]
}

So I need to add another configuration to this file:


"babel": {
     "presets": [
          ["es2015", {
               "modules": false
          }],
          "stage-1"
     ],
     "plugins": [
          ["istanbul", {
               "exclude": [
                    "**/*.spec.js"
                ]
          }]
     ]
}

You’ll notice a line about exclusions. This plugin by default will exclude certain naming conventions from your code coverage. If you don’t use one of the standards (which sometimes I don’t) it will include the test code files as part of your code coverage report – not good!  So I just add in the path to my test files to prevent this.

We also need to remove the coverage reporter from the files but leave the coverage reporter settings. This seems wrong but I promise this works. We also don’t want to include source files in our karma.conf.js file because right now we only want to get coverage for files that we have created a test file for. Why? Well what if we had a vendor script? If we included that file we’d be penalized for not writing unit tests on the file or maybe we’d write unit tests on the file anyway, which could be troublesome if it’s not written for testability. Ideally third-party and vendor scripts have been tested during the development phase.


const tests = './tests/**/*.spec.js';

const karmaConfig = {
    frameworks: ['mocha', 'sinon-chai'],
    files: [
        tests
    ],
    preprocessors: {
        tests: ['webpack']
    },
    webpack: webpackConfig('test'),
    reporters: ['coverage'],
    browsers: ['PhantomJS'],
    client: {
        captureConsole: false
    },
    specReporter: {
        showSpecTiming: true
    },
    reportSlowerThan: 25,
    coverageReporter: {
        dir: 'coverage',
        reporters: [
            { type: 'text' },
            { type: 'text-summary' },
            { type: 'html' }
        ]
    }
};

karmaConfig.preprocessors[tests] = ['webpack'];

module.exports = function (config) {
    config.set(karmaConfig);
}

Now when we run our tests we will get coverage only on the files that we want to have coverage for. Look out for posts on how to write unit tests and how to write your code to be testable in the future. There will also be a post on how to test third-party scripts, because sometimes it’s a critical component and you need the certainty that upgrades won’t break functionality on the site.

Pin It
Read More Leave comment

Simple NodeJS Web Server

I’m working on a personal project where I need to spin up a simple web server to serve up some deployable assets. In order to get a low overhead server I’m looking into a NodeJS web server.  So I set out to create one. This is the first, and simplest, one I could fine. It gets the job done but I think I probably need something more feature rich. This is as bare bones as it gets.  Let’s dig into this.

package.json:

"dependencies": {
    "connect": "^3.5.0",
    "serve-static": "^1.11.2"
}

This web server, outside of Node, only requires two packages: connect and serve-static. Then, I create a server.js file with the following code:

const connect = require('connect'),
      serveStatic = require('serve-static');

const port = '8080';
const location = './dist';

connect().use(serveStatic(location)).listen(port, function() {
    console.info('Server running on http://localhost:'+port+'...');
});

In a command prompt that’s navigated to the location where this code is located, type node server.js and it starts the web browser.

This initializes a simple web server at http://localhost:8080 and then loads up anything loaded in the location variable. It works, but doesn’t really have any features other than this. So I’ll be looking for a second version of this, but wanted to share this one as it’s super simple and in a pinch can be used to spin up the most basic of web servers.

Pin It
Read More Leave comment