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

You must be logged in to post a comment.