JS Testing Survival (mocha, chai, sinon)

This post is a simple guide to JS testing with Mocha, Chai, Sinon on CircleCI. It will show you how to setup for testing, some great tips for good coverage and more.
I’ll cover some best practices I use for testing JS code. It’s not official best practices, but I use these concepts as I found they make it easier to get easy to read test with full converge and a very flexible setup.

This post will dictate a unit test file to see the different points I found helpful when composing unit test files:

Setup

mocha is a testing framework for js, that allows you to use any assertion library you’d like, it goes very commonly with Chai. Chai is an assertion library that works with mocha. chai You can read there about how mocha and chai work, how to use it and more.
One of chai’s strong points is that you can easily extend it using support libraries and plugins. We will use a few of them, so let’s first setup our dependencies in our project:

chai – the chai library, has a good reference for how to use chai to assert or expect values, and a plugin directory – This is a valuable resource!

chai-httpchai-http – This is a chai extension that allows us to hit http endpoints during a test.

chai-as-promised – mocha support tests / setup that return a promise. This enables us to assert / expect what the result of the promise would be. We will see this in action shortly.

co-mocha – a mocha extension that allows us to use generator functions inside mocha setup / mocha test. If you do not do this step, and try to use a generator function, the test will finish and will not run yield correctly in test code. This means you will have twilight zone like results, of tests passing when they should fail!

sinonjs – cool test mocks, spies and stubs for any JS framework. Works really well, and very extensive

After we install all the packages, let’s create a new file, and add all the required libraries to it as follows:

1

2

3

4

5

6

7

8

9

10

11

12

13

//demo test file

constchai=require('chai');

constchaiHttp=require('chai-http');

constserver=require('../server');

constchaiAsPromised=require('chai-as-promised');

require('co-mocha');

constsinon=require('sinon');

constTestUtils=require('./utils/TestUtils');//explained later on.

constserver=require('../server.js');//explained later on

In this example I’m testing an express server, but you can use any type of node http server (assuming you are testing a server). Just make sure you export the server from you main or server file, and then you can require it from your test files.

1

2

3

4

5

6

7

8

9

10

We will see how we usethe server later on inthe test.

//server.js

constexpress=require('express');

constserver=express();

//all server route and setup code.

module.exports=server;

Grouping tests using ‘describe’

Mocha does a great job at grouping tests. To group tests together, under a subject use the following statement:

1

2

3

describe('Test Group Description"',()=>{

// test cases.

}

‘describes’ are also easily nest-able, which is great. So the following will also work:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

describe('Test Endpoint "',()=>{

describe('GET tests "',()=>{

// GET test cases.

}

describe('POST tests "',()=>{

// POST test cases.

}

describe('PUT tests "',()=>{

// PUT test cases.

}

describe('DELETE tests "',()=>{

// DELETE test cases.

}

}

This groups them together, and if you’re using something like intelliJ or webstorm then the output is displayed in a collapsible window very nicely:

Test hooks

When running tests many times we need to do setup before each test, before each test suite. The way to do that is to use the testing hooks before, after, beforeEach and afterEach:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

describe('hooks',function(){

before(function(){

// runs before all tests in this block

});

after(function(){

// runs after all tests in this block

});

beforeEach(function(){

// runs before each test in this block

});

afterEach(function(){

// runs after each test in this block

});

// test cases

});

Also these hooks can return a promise, the test framework will not continue until the promise is resolved, or will fail it is rejected:

1

2

3

4

5

6

7

8

before(()=>{

// do some work, return promise / promise chain

returnnewPromise(()=>true);

}

after(()=>functionThatReturnsPromise());

Also since we have require co-mocha, our hooks can also run a generator function:

1

2

3

4

5

6

7

8

let stuffINeedInTests=null;

before(function*(){

constresult=yield functionThatReturnsPromise();

constresultFromGen=yield*generatorFunction();

stuffINeedInTests={promiseResult:result,genResult:resultFromGen}

}

I can then use the stuffINeedInTest in my test files. You can also do this setup using promises as shown above.

Hook on root level

Test hooks are awesome, but sometimes we might want some hooks to run not only once for a test file, but once for all our tests. mocha does expose root level hooks, so in order to achieve that we will create a new hooks file: root-level-hooks.js
and put our hooks in there with no describe block around it:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

//root-level-hooks.js

require('co-mocha');//enable use of generators

before(()=>{

//global hook to run once before all tests

});

after(function*(){

// global after hook that can

// call generators / promises

// using yield / yield*

});

Then at the top of each test file we will require this file in:

1

2

3

4

5

6

7

//demo test file

require('./root-level-hooks');

//demo test file 2

require('./root-level-hooks);

This way our hooks will run once for all test runs. This is the perfect place to load up a test db, run some root level setup, authenticate to system etc.

External System Mocking

Some systems / modules call other systems internally . For example think of a functions that processes a payment for an order. That function might need to call a payment gateway, or after the order is processed, send the shipping information to a another system (for example a logistics system or upload a file to s3). Unit test are intended to be very stand alone, and not depend on external systems. Therefore we need a way to mock those external systems, so when the tested code reaches out to these systems ,the test case can respond on its behalf.

In our test we will use sinon.
Basically we will mock the calls using a test class or mocked calls, that reads a response file and send it’s back.
This makes the mock strait forward:

What we are doing here is creating a mock object, in this case we are mocking the axios, as my server code uses it, but we can use the same construct to mock any external system.
Our request mock will provide a get and a post methods, just like the axios library does. I’m using the sinon.spy to check what URL is requested by the module code, and a switch statement to handle the different urls requested by the module. Our mock can return urls, json, promises, files, or whatever is needed to successfully mock the external system.

1

2

3

4

5

6

7

8

9

10

11

constaxios=require('axios')

before(()=>{

sinon.stub(axios,'get').callsFake(requestMock.get);

sinon.stub(axios,'post').callsFake(reqeustMock.post);

});

after(()=>{

axios.get.restore();

axios.post.restore();

});

I’m then using the before hook to register the mock as axios mock, so when the module called require(‘axios’) it will receive my mock and not the node_module that actually does the http request.

Then I’m using the after hook, to disable the mock and return to normal.

Test Cases

Mocha let’s us create tests very easily. You use the ‘it’ keyword to create a test.
Either:

1

2

3

4

5

it('Unit test description and expected output',()=>{

returnvalue orreturnapromise.

});

Or using generators

1

2

3

4

5

it('Unit test description and expected output',function*(){

//yield generator or promise.

});

You can also use the done callback, but I prefer not to use it.
I like to keep code a small as possible, and without any distractions.
However it’s here if you need it

1

2

3

4

5

it('Unit test description and expected output',(done){

//call done when finished some async operation

});

<

1

Each test case is composed out of two parts:
1) The test itself
2) Expected result

Test themselves

Since we have added the mock for external system we can safely use our test code to hit a function, or if we are testing a rest endpoint we can call that endpoint:

1

2

3

4

5

6

7

8

9

10

11

12

13

chai.request(server)

.get('/serverPath')

.then(function(response){

// process response

});

//or

constresponse=yield chai.request(server)

.post('serverPath')

.send({testObject:{name:'test'}});

In this example we are testing an endpoint, but calling a function would have been even easier.

Expected Result

The second part is includes looking at the results of our test runs and we will be using chai to look at the responses. chai provides a long list of ways to look at responses either using expect, should or assert, whichever you prefer.
I try to use expect often as it doesn’t change the Object.prototype. Here is a discussion on the differences expect vs should vs assert

1

2

3

4

5

6

7

expect(res).to.have.property('statusCode',200);

expect(res).to.have.property('body);

assert.isOk(res.statusCode === 201, 'Bad status code');

TestUtils.testForSucessAndBody(res,expect,201);

TestUtils.test

Failing these will trigger the test to fail.
I normally use a test helper class with a few standard ways to test for correct response and to compare return object to the expected object:

Test for failures

Using promises, I can also quickly test for failures to ensure our code doesn’t only work properly for valid input, but it should also work for invalid input.

I then require the TestUtil class in my test file, and then I can use the test utils for quickly expecting or asserting different conditions.

Mocha tests on circle

When using CircleCI, it’s great to get the output of the test into the $CIRCLE_TEST_REPORTS folder, as then circle will read the output, and present you with the results of the test, rather than you looking through the logs each time to figure out what went right and what went wrong. Circle guys have written a whole document about that, and you can see it CircleCi Test Artifacts.

In our discussion we will focus on using mocha and getting the reports parsed. In order to do so, we need mocha to output the result in junit xml format. This can be achieved easily using the mocha-junit-reporter. This lib will allow mocha to run our test and outpu the results in the correct format.

This output the information in the junit folder for both eslint (if you are using it) and for mocha.

Now all that is needed is to create a link between your junit folder and the CIRCLE_TEST_REPORTS, which can be done by editing the circle.yml file and adding the following line in the pre step for test.

1

2

3

4

5

6

7

test:

pre:

-mkdir-p$CIRCLE_TEST_REPORTS/junit

# for none docker:

If you aren’t using docker, you can also add a symbolic link after the creation of the folder - ln -s $CIRCLE_TEST_REPORTS/junit ~/yourProjectRoot/junit

However if you are using docker-compose, or docker run to execute your test inside a will also need to add a volume that maps you test output to the CRICLE_TEST_REPORTS.
For docker compose:

1

2

3

4

volumes:

-$CIRCLE_TEST_REPORTS/junit://junit

for docker run you can do the same with using the -V command.
Once that is done, you’ll get the report output in circle after the build finishes.