DEV Community

Samuel Raphael
Samuel Raphael

Posted on

Testing A Node/Express Application With Mocha & Chai

alt text
You have spent a lot of time and effort writing up a fairly medium application, let’s say with a code base of about 1000 lines and you’ve manually tested the application to make sure everything is running fine. You push your code to Github and someone decides to contribute their own quota to your work. They push their code, created a pull request and you merge it in, now your application no longer runs, everything is broken, all because of the code you merged in. In other to avoid this type of problem and many more that come with software development, you need to integrate testing into your workflow.

Testing requires that you write tests which cover various input a software might receive and the corresponding output to them. That way you can be sure that the application is running exactly how you intended it to and this can prevent a lot of bugs. It is always important to write a test before new code is added to your code-base so you can be sure the new code is not introducing any bug to your code base and also it helps you know beforehand if the new code is breaking any part of your code base.

In this article, we’ll be writing a simple Node/Express API application while incorporating testing using the mocha & chai JavaScript testing packages.

Mocha, according to the description on its website, is a testing framework that makes asynchronous testing simple and fun. It will provide the testing environment that makes it easy for us to run chai.

Chai is an assertion library that can be paired with any test framework. It is the library that we’ll actually write our tests with.

Setting up our sample application

We’ll be building out an application that reads information from a non-persistent students record data. To continue, we need to have the following files and folders created:

---controllers/
------studentController.js
---dummy/
------students.js
---routes/
------index.js
---tests/
------test.js
---.babelrc
---server.js
---package.json
Enter fullscreen mode Exit fullscreen mode

To set up our dummy data, we need to include the data in the dummy/students.js file:

const students = [
   {
     id: 1,
     name: 'Sean Grey',
     age: 24,
   },
   {
     id: 2,
     name: 'John Doe',
     age: 26,
   },
   {
     id: 3,
     name: 'Janet Dane',
     age: 19,
   },
];
export default students;
Enter fullscreen mode Exit fullscreen mode

The code block above assigns an array of objects, each object holding the details of a student.

Now let’s set up our package.json, so we can install all the packages we would be needing to build out and test our application.

{
   "name": "student-record",
   "version": "1.0.0",
   "description": "An API to manage student record",
   "main": "server.js",
   "author": "Samuel Afolaranmi",
   "license": "MIT",
   "scripts": {
        "test": "mocha --require babel-register tests/*.js --exit",
        "dev": "nodemon --exec babel-node --presets babel-preset-env ./server.js"
   }
   "dependencies": {
        "body-parser": "^1.18.3",
        "express": "^4.16.3"
   },
   "devDependencies": {
        "babel-cli": "^6.26.0",
        "babel-preset-env": "^1.7.0",
        "chai": "^4.1.2",
        "chai-http": "^4.0.0",
        "mocha": "^5.1.1",
        "nodemon": "^1.17.4"
   }
}
Enter fullscreen mode Exit fullscreen mode

In the package.json file, we include our mocha and chai, which we’ll be using to write our tests. We also needed to include chai-http which is a plugin that allows us to run HTTP integrations with chai assertions. We can now run npm install to install the packages and get ready to finish setting up our application.

The next step is to create our routes and server.js files, but first, we should create our controller file as we would be needing to import it into our routes file. In the controllers/studentController.js file, we should include:

import students from '../dummy/students.js';
class StudentController {
    // Get all students
    static getAllStudents(req, res) {
          return res.status(200).json({
                students,
                message: "All the students",
          });
    }
    // Get a single student
    static getSingleStudent(req, res) {
           const findStudent = students.find(student => student.id === parseInt(req.params.id, 10));
           if (findStudent) {
               return res.status(200).json({
                     student: findStudent,
                     message: "A single student record",
               });
           }
           return res.status(404).json({
                 message: "Student record not found",
           });
    }
}
export default StudentController;
Enter fullscreen mode Exit fullscreen mode

In the controllers/studentController.js file, we imported our dummy data, created a class to hold our controller methods and created two static methods each for what we want to achieve with the controller class. The first method, getAllStudents, as the name implies gets all the students record we have in our dummy data and returns them with a 200 HTTP status code, while the second method, getSingleStudent, gets the record of a single student and returns it with a 200 HTTP status. If a record is not found, a 404 HTTP status code is returned.

Now that we have our controller set up, we can now go back to working on our routes and server.js. In our routes/index.js file, we should add the following code:

import { Router } from 'express';
import StudentController from '../controllers/studentController.js';
const routes = Router();
routes.get('/', StudentController.getAllStudents);
routes.get('/:id', StudentController.getSingleStudent);
export default routes;
Enter fullscreen mode Exit fullscreen mode

We imported Router (express router) from express and assigned it to routes, we also imported our StudentController class from our controllers/studentController. js file. We used the Router we imported to create two routes, which are tied respectively to their corresponding controller methods.

Now we should create our server.js file so we can test the code we’ve been writing if it works.

import express from 'express';
import bodyParser from 'body-parser';
import routes from './routes/index';
// Instantiate express
const app = express();
// Set our port
const port = process.env.PORT || 8000;
// Configure app to user bodyParser
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// Register our routes in app
app.use('/', routes);
// Start our server
app.listen(port, () => {
    console.log(`Server started on port ${port}`);
});
// Export our app for testing purposes
export default app;
Enter fullscreen mode Exit fullscreen mode

Because we’re writing ES6 code, we need babel to compile our code, and for that to work, we need to add the following code to our .babelrc file:

{
   "presets": ["env"]
}
Enter fullscreen mode Exit fullscreen mode

Now that we have our application all set up, we can go ahead to run npm run dev to run our application and test our endpoints using Postman.

Writing tests for our application

Our application works well, but we need to write tests for it. To make sure we don’t break it, while also covering all edge cases. In our tests/test.js file, we’ll write our tests.

// Import the dependencies for testing
import chai from 'chai';
import chaiHttp from 'chai-http';
import app from '../server';
// Configure chai
chai.use(chaiHttp);
chai.should();
describe("Students", () => {
    describe("GET /", () => {
        // Test to get all students record
        it("should get all students record", (done) => {
             chai.request(app)
                 .get('/')
                 .end((err, res) => {
                     res.should.have.status(200);
                     res.body.should.be.a('object');
                     done();
                  });
         });
        // Test to get single student record
        it("should get a single student record", (done) => {
             const id = 1;
             chai.request(app)
                 .get(`/${id}`)
                 .end((err, res) => {
                     res.should.have.status(200);
                     res.body.should.be.a('object');
                     done();
                  });
         });

        // Test to get single student record
        it("should not get a single student record", (done) => {
             const id = 5;
             chai.request(app)
                 .get(`/${id}`)
                 .end((err, res) => {
                     res.should.have.status(404);
                     done();
                  });
         });
    });
});
Enter fullscreen mode Exit fullscreen mode

At the beginning of the file, we imported all packages needed to make the test run, then we configured chai to use the chai-http plugin. We also configured chai to use the should interface by running chai.should(). Each describe blocks are used to group our tests together for easier access and better organization.

The first it block is a test that runs on the first endpoint to get all student record from the data, it asserts that the response should have a status of 200 and it should return an object. The second it block is a test that runs on the second endpoint to get a single student request. Assuming the student exists, it asserts that the response should have a status of 200 and it should return an object. And finally, the third it block is a test that runs also on the second endpoint to get a single request. Assuming the student does not exist, it asserts that the response should have a status of 404.

All that is remaining is for us to run npm run test and we’ll see our tests passing before our very eyes. Beautiful, isn’t it?

Top comments (7)

Collapse
 
higustave123 profile image
GUSTAVE HARINTWARI

Hi. thx for the tutorial.it is clear and easly understandable. I would like to ask whether you have a material of testing routes that has a jwt(json web token) token. I mean authenticated test.thanks. my email is higustave123@gmail.com

Collapse
 
wombolo profile image
Dídùnlolúwa • Edited

Hi Samuel, this was a great & helpful post.
But I seem to run into issues when I run istanbul cover ./node_modules/mocha/bin/_mocha tests/*.js command for testing
The test never seems to terminate.
What might be the issue here?

Collapse
 
kiddeveloper profile image
Samuel Raphael

Hi Didunloluwa,

Thanks for reaching out.

I think you need to add this flag --exit to the end of the command. That tells the test to exit once it's done running all it blocks.

I hope that helps

Collapse
 
saslamp profile image
Abubakar Sambo

My test for /:id route is failing. I followed your example

Collapse
 
kiddeveloper profile image
Samuel Raphael

Hi Abubakar, can you kindly post the error message you get here, so we can look into it together. Thanks

Collapse
 
jasoniyi profile image
Jasoniyi

my tests for :id fails too. it shows "Timeout of 6000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves."

Collapse
 
kiddeveloper profile image
Samuel Raphael • Edited

Hi Jasoniyi,

Thanks for reaching out.

I think you need to add this flag --exit to the end of the command. That tells the test to exit once it's done running all it blocks.

I hope that helps