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
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;
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"
}
}
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;
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;
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;
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"]
}
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();
});
});
});
});
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)
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
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 testingThe test never seems to terminate.
What might be the issue here?
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 allit
blocks.I hope that helps
My test for /:id route is failing. I followed your example
Hi Abubakar, can you kindly post the error message you get here, so we can look into it together. Thanks
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."
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