Mocha as a JavaScript Test Framework

The last few projects I discussed were to get you started coding in ES6 JavaScript. I intentionally kept the functionality very limited. In this article, I will show how to set up a test framework using my ES6 CLI Promises repository, promises. There are many different frameworks to choose from. A simple one to set up is Mocha.

Setting up

First, clone promises (the instructions for cloning are included in the article that goes with this repository, My JS Journey: Promises).

Next, install the node dependencies.

npm install

Then, build and run it to ensure everything is set up correctly.

npm run build
npm start

It should say,
hello world

Install Mocha

Install Mocha.

npm install mocha --save-dev

This should have added mocha to the devDependencies in package.json. Now is a good time to change the name.

{
  "name": "MochaExample",
  "version": "1.0.0",
  "description": "Simple Mocha example",
  "main": "index.js",
  "scripts": {
    "build": "babel src -d dist",
    "start": "node dist/index",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-cli": "^6.24.0",
    "babel-core": "^6.24.0",
    "babel-preset-env": "^1.3.2",
    "babel-register": "^6.24.0",
    "mocha": "^3.3.0"
  }
}

Might as well check in the code.

Convert src/hello.js to Implement a class

To facilitate exporting, I find it easiest to use classes. Modify src/hello.js as follows.

class Hello {
  static getWorld() {
    return new Promise((resolve) => {
      setTimeout(() => resolve(' world'), 500);
    });
  }

  static hello() {
    return new Promise((resolve) => {
      this.getWorld().then(result => {
        resolve('hello' + result);
      });
    });
  }
}
export default Hello;

Notice that the functions changed from const to static. Also, now that they are part of the object, to reference them within the object, it is necessary to prefix them with this., e.g. this.getWorld(), as in line 10.
Modify src/index.js to accommodate the new class.

import Hello from './hello';

const sayHello = (message) => {
  console.log(message);
};

const main = () => {
    var args = process.argv;
    if (args[2] && args[2] === '--v' ) {
        console.log('Es6CliSkeleton - version 1.0.0');
    } else {
        Hello.hello().then(res => sayHello(res));
    }
};

main();

The lines that changed are 1 and 12, both of which reference the new Hello class.

Build and run to ensure everything still works.

Adding Tests

First, create a folder test. Add a file to this folder called hello_test.js.
Add the first test.

import assert from 'assert';
import Hello from '../src/hello';

describe('Hello', function() {
  describe('getWorld', function() {
    it('should return space world', function() {
      Hello.getWorld()
      .then(result => {
        assert.equal(' world', result);
      });
    });
  });
});

Next, in package.json, modify the “scripts” section’s “test” to “mocha –compilers js:babel-core/register test/*_test.js”.
Build and run the test.

npm run build
npm run test

You should get output something like this.

> MochaExample@1.0.0 test /Users/your_src/promises
> mocha --compilers js:babel-core/register test/*_test.js
  Hello
    getWorld
      ✓ should return space world
  1 passing (100ms)

With a nice green checkmark.
Check the code in.

A Second Test

We should have a test on the other function, Hello.hello(). It will be very similar the first one. Add this right code after the other test.

  describe('hello', function() {
    it('should return hello world', function() {
      Hello.hello()
      .then(result => {
        assert.equal('hello world', result);
      });
    });
  });

Build and run. The output should look like this.

  Hello
    getWorld
      ✓ should return space world
    hello
      ✓ should return hello world

  2 passing (65ms)

Go ahead and check that in since the next section covers something a little different.

A Broken Promise

Let’s see what happens when the promise is broken. Modify what the promise returns in Hello.getWorld() from

  setTimeout(() => resolve(' world'), 500);

to

  let input = "myErr";
  if (input === "myErr") { reject(new Error(input)); }
  setTimeout(() => resolve(' world'), 500);

Build and run the app.

> MochaExample@1.0.0 start /Users/your_src/Closet/promises
> node dist/index

(node:61097) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): 42

It fails, kind of how you might expect. Run the tests.

  Hello
    getWorld
      ✓ should return space world
(node:61087) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: myErr
    hello
      ✓ should return hello world
(node:61087) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4): Error: myErr

  2 passing (111ms)

Interestingly, the tests pass, although they report the unhandled promise rejection. Let’s fix that first. Modify the tests to include a catch, which will also require the use of done in lines 6, 10 and 14, and 19, 23 and 27.

import assert from 'assert';
import Hello from '../src/hello';

describe('Hello', function() {
  describe('getWorld', function() {
    it('should return space world', function(done) {
      Hello.getWorld()
      .then(result => {
        assert.equal(' world', result);
        done();
      })
      .catch(err => {
        console.log(err);
        done(err);
      });
    });
  });
  describe('hello', function() {
    it('should return hello world', function(done) {
      Hello.hello()
      .then(result => {
        assert.equal('hello world', result);
        done();
      })
      .catch(err => {
        console.log(err);
        done(err);
      });
    });
  });
});

Now, the tests fail and let you know why:

  1) Hello getWorld should return space world:
     Error: myErr
      at src/hello.js:5:39
      at Function.getWorld (src/hello.js:3:12)
      at Context.<anonymous> (test/hello_test.js:7:13)

  2) Hello hello should return hello world:
     Error: myErr
      at src/hello.js:5:39
      at Function.getWorld (src/hello.js:3:12)
      at src/hello.js:12:12
      at Function.hello (src/hello.js:11:12)
      at Context.<anonymous> (test/hello_test.js:20:13)

Remove the changes to getWorld(). Check in the code.

In Conclusion

I generally start by writing my tests (Test Driven Development or TDD). I believe it is one of the fundamental practices in good code development. It is important to ensure you have complete test coverage. In this example, you can see I had tests for the “happy path” but had not covered potential failures. It was a bit contrived, but be on the lookout for things that can go wrong. In the future, I will show how to use Node fs, which is their file system interface. It has a lot of good examples where you need to test for things that might go wrong—a missing file, an empty file, invalid permissions—lots of things to test for.

You can find the code for this project in my github repository, mocha-example.

Copyright ©2014-17 Ramona Ridgewell. All rights reserved.

Advertisements
This entry was posted in #Education, AmWriting, Education, GitHub, JavaScript, Programming, Science, Software Development, STEM and tagged , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s