What You Will Build

In this article, we shall go through all steps required to write tests for the To-do application API endpoints built in the previous session using Chai (a Behavioral Driven Development/Test Driven Development assertion library for Node and the browser) and Mocha (a JavaScript testing framework).

Prerequisites

In order to follow along with this article series, firstly you should have gone through last two series on API which are “Building RESTful APIs using Express and Node with MongoDB Atlas” and “Securing Node and Express RESTful API with Json Web Token (JWT)” respectively. Also, prior knowledge around JavaScript and an ideal understanding of Nodejs is needed or required you might not understand the article well and it might make sense to learn about it first. The code and the concepts explained in this article are not complex, therefore, with a previous experience with JavaScript, and a bit know how about Node.js, you won't have a hard time here.

Introduction to Chai and Mocha for Unit Testing

One of the important tasks which most of the developers ignores is writing unit tests for your code. Unit test is not just a help to tester or quality assurance team but it’s the proof of quality of your code. To create our unit tests, we will be using Mocha, Chai, and Chai HTTP modules.

Chai is a Behavior Driven Development / Test Driven Development assertion library for Node and the browser and can be used with any testing framework (like Mocha). These are (assertions) simple statements that are always expected to evaluate to true, and if not, they throw an error

Mocha is a framework that does not have a built-in assertion library like Chai, but its developed to run test cases and reports any errors encountered during the process.

Also, with a need to communicate with the application/server, returning the responses, and using Chai assertions to verify the results, Chai-http helps handle all of these, It is a module for sending HTTP requests.

Some functions of Mocha and Chai framework for performing tests are:

  • describe(): This is used to associate multiple tests in one collection.

  • it(): This is used in a single test unit to mention how the test should behave and a callback to execute the test body.

  • chai.request(): This is used to make an HTTP Request to the REST API

  • done(): This is used to mention that the test was successful.

  • xx.should.xxx(): The function should help to assert the test condition that is make test assertions. If the condition fails, the test fails.

The above mocha and chai functions will give us the enablement to do the following:

  • Test for HTTP status response codes

  • Ttest for a string of text on a page

  • Test for a json response and validate the properties of the object

  • Write tests that not only verify the response of your application, but the behavior as well

Getting Started With Unit Testing with Chai and Mocha

Outline

  • Updating To-do API folder structure
  • Installing Dependencies
  • Write and Run Test for unauthenticated Todo API endpoints
  • Write and Run Test for an authenticated Todo API endpoints

Updating To-do API folder structure

Firstly, we need to update the File structure of the existing To-do API directory by some the following command line code below in other to add some new files and folders.

mkdir -p api/test
touch api/test/test.server.js

At the end of this implementation our expected file structure is seen below.

Todo-API/
├──api/
│  ├── controllers/  * API Controller folder
│  │  └── todoController.js  * To-do controller file
│  │ └── authController.js  * User Authentication controller file
│  │
│  ├── models/ * API Model folder
│  │  └── todoModel.js * To-do model file
│  │  └── userModel.js * User model file
│  │
│  ├── routes/ * API route folder
│  │  └──todoRoutes.js * To-do route file
│  │
├── config/
│  └──db.js * Db connection file
│  │
├── **test/ * test folder
│ └──test.server.js * To-do route file**
│  │
├── package.json  * Defines our JavaScript dependencies
├── package-lock.json  * ----------
├── server.js * API server file

All your files and folders are presented as a tree in the file explorer. You can switch from one to another by clicking a file in the tree.

Installing Dependencies

Let’s run the snippet below in the command line to install other dependences to be used.

npm install chai
npm install mocha
npm install chai-http

Having gone through the previous part of this series, it’s assumed that we already done the following.

  • Cluster created in MongoDB Atlas cloud database.
  • Schema and model created for Todo
  • Database connected to Application
  • Express Application created
  • Application API Endpoints defined
  • Schema and Model created for User
  • Controller functions for User Authentication created
  • JWT Token signed
  • API Endpoint and Routes for Login and Register defined
  • User Authentication Controller applied on To-do Application endpoints
  • JWT Token Verified

With a solid understanding of the above, we can now start to write the unit test for API endpoints.

Write and Run Tests for unauthenticated Todo API endpoints

First, we are going to start with testing the unauthenticated endpoints. Open the test.server.js file inside the api/test directory and follow the steps in the snippet below.


    // Endpoint testing with mocha and chai and chai-http

    // Import libraries 
    const chai = require('chai');
    const chaiHttp = require('chai-http');

    const should = chai.should();
    var mongoose = require("mongoose");

    // Import server
    var server = require('../server');

    // Import Todo Model   
    var Todo = require("../api/models/todoModel");

    // use chaiHttp for making the actual HTTP requests   
    chai.use(chaiHttp);
    describe('Todo API', function() {
        beforeEach(function(done) {
            var newTodo = new Todo({
                text: 'Cook Indomie',
                status: true
            });
            newTodo.save(function(err) {
                done();
            });
        });

        afterEach(function(done) {
            Todo.collection.drop().then(function() {

                // success     
            }).catch(function() {

                // error handling
                console.warn(' collection may not exists!');
            })
            done();
        });

        it('should list ALL Todos on /todos GET', function(done) {
            chai.request(server)
                .get('/todos')
                .end(function(err, res) {
                    res.should.have.status(200);
                    res.should.be.json;
                    res.body.should.be.a('array');
                    res.body[0].should.have.property('text');
                    res.body[0].should.have.property('status');
                    res.body[0].should.have.property('_id');
                    done();
                });
        });

        it('should add a todo on /todos POST', function(done) {
            chai.request(server)
                .post('/todos')
                .send({
                    'text': 'Cook Indomie',
                    'status': true
                })
                .end(function(err, res) {

                    // the res object should have a status of 201
                    res.should.have.status(201);
                    res.should.be.json;
                    res.body.should.be.a('object');
                    res.body.should.have.property('text');
                    res.body.should.have.property('status');
                    res.body.should.have.property('_id');
                    res.body.text.should.equal('Cook Indomie');
                    res.body.status.should.equal(true);
                    done();
                });
        });

        it('should update the status of a Todo on /todos/<id> PUT', function(done) {
            chai.request(server)
                .get('/todos')
                .end(function(err, res) {
                    chai.request(server)
                        .put('/todo/' + res.body[0]._id)

                        // this is like sending $http.post or this.http.post in Angular\
                        .send({
                            'text': 'Cook Indomie',
                            'status': false
                        })
                        // when we get a response from the endpoint
                        // in other words,
                        .end(function(error, response) {
                            // the res object should have a status of 200
                            response.should.have.status(200);
                            response.should.be.json;
                            response.body.should.be.a('object');
                            response.body.should.have.property('text');
                            response.body.should.have.property('status');
                            response.body.should.have.property('_id');
                            response.body.text.should.equal('Cook Indomie');
                            response.body.status.should.equal(false);
                            done();
                        });
                });
        });
        it('should delete a todo on /todo/<id> DELETE without Auth Token', function(done) {
            chai.request(server)
                .get('/todos')
                .end(function(err, res) {
                    chai.request(server)
                        .delete('/todo/' + res.body[0]._id)
                        .end(function(error, response) {
                            response.should.have.status(200);
                            response.body.should.have.property('message');
                            response.body.message.should.equal('Todo successfully deleted');
                            done();
                        });
                });
        });
    });

From the above snippet, we imported libraries ( chai ,chaiHttp),server and model(Todo) then we used describe(“…”) to associate multiple tests into a collection and it(“..”) to mention how the test should behave and a callback to execute the test body. The chai.request(server) makes http request to the server and the should methods, for instance response.should.have.status(200) assert the test condition that http response from the server should have status as 200 . Then the done() method is called after a test was successful.

Before we run the test, let's export our API server. Go to server.js and export app.listen(...) as shown below:

    // Listen to server
    module.exports = app.listen(port, () => {
        console.log(`Server running at http://localhost:${port}`);
    });

Also, ensure the authentication controller functions is not added to todo route since we are testing for unauthenticated endpoints. Therefore, open todoRoutes.js file and update as shown below:

'use strict';

module.exports = function(app) {
    var todoList = require('../controllers/todoController');

    var userHandlers = require('../controllers/authController');

    // todoList Routes

    // get and post request for /todos endpoints app

        .route("/todos")
        .get(todoList.listAllTodos).post(todoList.createNewTodo); // update with login handler

    // put and delete request for /todos endpoints app

        .route("/todo/:id")
        .put(todoList.updateTodo) // update with login handler
        .delete(todoList.deleteTodo); // update with login handler

    // post request for user registration app

        .route("/auth/register")
        .post(userHandlers.register);

    // post request for user log in app

        .route("/auth/sign_in")
        .post(userHandlers.signIn);
};

To Run the tests for unauthentication endpoints Open package.json file and set script property as shown below:

"scripts": {

    "test": "mocha --timeout 10000000000"

},

run the command npm test , see the output as shown below.

Output for Unauthenticated endpoints testing: Unauthenticated endpoints

Write and Run Tests for an authenticated Todo API endpoints

In the session, we are going to write a test for an authenticated endpoint. Lets use the

    // Endpoint testing with mocha and chai and chai-http

    // Import libraries
    const chai = require('chai');
    const chaiHttp = require('chai-http');
    const should = chai.should();
    var mongoose = require("mongoose");

    // Import server
    var server = require('../server');

    // Import Todo Model
    var Todo = require("../api/models/todoModel");
    var User = require("../api/models/userModel");

    // use chaiHttp for making the actual HTTP requests        
    chai.use(chaiHttp);

    describe('Todo API', function() {

        it('should Register user, login user, check token and delete a todo on /todo/<id> DELETE', function(done) {
            chai.request(server)

                // register request
                .post('/auth/register')

                // send user registration details
                .send({
                        'fullName': 'Paul Oluyege',
                        'email': 'tester@gmail.com',
                        'password': 'tester'
                    }

                ) // this is like sending $http.post or this.http.post in Angular
                .end((err, res) => { // when we get a resonse from the endpoint

                    // in other words,
                    // the res object should have a status of 201
                    res.should.have.status(201);

                    // follow up with login
                    chai.request(server)
                        .post('/auth/sign_in')
                        // send user login details
                        .send({
                            'email': 'tester@gmail.com',
                            'password': 'tester'
                        })
                        .end((err, res) => {
                            console.log('this runs the login part');
                            res.body.should.have.property('token');
                            var token = res.body.token;

                            // follow up with requesting user protected page
                            chai.request(server)
                                .get('/todos')
                                .end(function(err, res) {
                                    chai.request(server)
                                        .delete('/todo/' + res.body[0]._id)

                                        // we set the auth header with our token
                                        .set('Authorization', 'JWT ' + token)
                                        .end(function(error, resonse) {
                                            resonse.should.have.status(200);
                                            resonse.body.should.have.property('message');
                                            resonse.body.message.should.equal('Authorized User, Action Successful!');
                                            done();
                                        });
                                })
                        })
                })
        })
    })

From the snippet above, since we are testing authenticated endpoints, we imported User model and use the chai functions(describes(), it() etc.) as used in the previous session, Post request made to /auth/register', registration details sent, then later to /auth/sign_in' and login details also sent. Should method in ‘res.body.should.have.property('token')’ assert the test condition that http response from the server should have a property token . The token is extracted from body and set with auth header as expected by the authenticated endpoints.

Before we run out test we need to ensure the authentication controller functions is added to todo routes/endpoints you intent to authenticate since we are testing for authenticated endpoints. Therefore, open todoRoutes.js file and update as shown below:

'use strict';

module.exports = function(app) {
    var todoList = require('../controllers/todoController');

    var userHandlers = require('../controllers/authController');

    // todoList Routes
    // get and post request for /todos endpoints app

        .route("/todos")
        .get(todoList.listAllTodos).post(userHandlers.loginRequired, todoList.createNewTodo); // update with login handler

    // put and delete request for /todos endpoints app
        .route("/todo/:id").put(userHandlers.loginRequired, todoList.updateTodo) // update with login handler
        .delete(userHandlers.loginRequired, todoList.deleteTodo); // update with login handler

    // post request for user registration app
        .route("/auth/register")
        .post(userHandlers.register);

    // post request for user log in app
        .route("/auth/sign_in")
        .post(userHandlers.signIn);
};

run the command npm test , see the output as shown below.

Output for authenticated endpoints testing: For Authenticated