Using Mocha for Test-Driven Development in building API with Node / Express
By: Bilguun Batbold
Tags: Testing, Nodejs, Programming, JavaScript

Image taken from Unsplash

Automated tests? Why? How?

For the longest time, I was just writing functionalities and manually testing them to ensure they work as expected. Every single new functionality, I would test my service manually ten times, hundred times, THOUSANDS times… you get it.

Some of my key problems with manual testing were:

Additional features may break existing functionality Code refactoring may break the intended logic No incentive to write clean code and maintainable code Huge waste of time, which could have been used to improve my application

I guarantee you that the extra effort put in writing tests will eventually pay off big time! So let’s get started!

What is TDD or BDD?

Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: first the developer writes an (initially failing) automated test case that defines a desired improvement or new function, then produces the minimum amount of code to pass that test, and finally refactors the new code to acceptable standards.

The following sequence of steps is generally followed:

Add a test

Run all tests and see if the new one fails

Write some code

Run tests

Refactor code

Repeat

So TDD ensures that we write logic that is actually tested and is of high quality. BDD (Behavior-Driven Development) is based on TDD and promotes collaboration between product managers and developers.

Let’s say we have the following array:

var menu = [ 'green', 'chai', 'oolong' ];

In TDD, we will verify like so:

assert.isArray(menu)

In BDD, we will instead write it like:

expect(menu).to.be.an('array');

We can write more complex assertions but this is the gist of it. You can clearly see that BDD style is much more readable, even for non developers.

Prerequisites

Now that we have the basics of why its of great importance to write tests, let’s get down to actual implementation.

I assume that you have had previous knowledge of creating APIs in Express, but feel free to follow along even if you do not have prior experience. These are the required installations before we get started:

Node / NPM MongoDB installed and running

Tutorial

We will be creating a simple REST API that will handle CRUD operations on users.

Let’s create a new folder called nodejs-testing. Go to the folder and run the following:

npm init --yes

npm i express mocha chai supertest nyc mongoose

We will first initialize a new npm project with default parameters, and add the required libraries.

Mocha / Chai to write tests Supertest to mock requests Nyc for test coverage report Mongoose for creating models and MongoDB interactions

Let’s first create our bare minimum express app. Make sure your project structure looks like this:

Project structure

User.model.js

First we create a new schema to define a new user schema. For simplicity, we will only use 3 properties for the user.

App.js

Next we write some logic in our app.js. Here we are connecting to our MongoDB database, use our usersRouter (which is empty at the moment, it is fine) and create error on any end points that are not available. Finally we export the app. We export the app so that we can do tests with the app, without listening to any ports. The actual startup happens in bin/www

./bin/www

Here we just create a HTTP server and listen to incoming requests in production mode (or development).

Now that we have the super basic skeleton, let’s try running the app!

node bin/www

test server listening on port 3000

Connected to MongoDB at mongodb://localhost/tddDB...

Great! Our server is listening on port 3000! When we actually try to request any resource from localhost:3000, we get the following message {“message”:”Not Found”}. And that is alright, we will get to it in a minute.

Writing Tests

The reason why we left user.controller and user.route empty is because I wanted to demonstrate how you can use TDD approach to write your application logic. We will first write the tests, let it run and fail, and then add the logic to make it pass. Repeat until all our desired tests pass!

Before we start writing tests, we need to ensure that our tests have a proper setup and tear down in place. This is to ensure that each test begins from a fresh state, so that it can be predictable and reproducible. Learn more about Mocha Hooks here.

Let’s look through this basic integration test that tests our api/users end point. We use describe to group the tests together and add some structure to it. We then use it to write the individual tests.

First we have the main grouping, describe("api/users") Then, before each individual test, we will clear our Users collection in MongoDB. In our describe("GET /") block, we insert few users into the database, and expect some result. Note that we are using BDD style, by using Chai Expect. It is really easy to understand, for example, when we write expect(res.status).to.equal(200); we can immediately know that we expect the response status to be 200, and to also have a body array of length 2.

Running Tests

To run the test, we simply do the following:

mocha --timeout 10000 --exit

We increase the default timeout parameter to 10 seconds, just to ensure that our database operations have sufficient time to execute.

Test output

You will then receive a similar output as shown above. Out describe and it statements are properly shown and we can see where the tests pass and fail with proper structure.

Adding logic to pass the tests

Now that we have written our expected results via the tests, let’s add the logic to pass them.

User.controller.js

User.route.js

Add the codes to the controller and the routes as shown above. In our controller, we have 2 methods. One gets all users, and one gets a specific user. We also check if the object id passed is valid or not, and send that responses accordingly to how we have defined our tests to behave.

Run the test again and the output should show:

Great! We have done our first round of TDD! Next we can add more tests to handle POST, PUT and DELETE. I will just add some simple logic but please feel free to expand on them accordingly.

I have added few tests here to ensure that our CRUD operations are working. Note that this is not the complete set, more tests have to be written to ensure code stability, but it can be a starting point for you.

Controller has been updated with few more methods

Router has been updated to support more CRUD operations

Let’s run the test again!

Our output with 8 tests

Nice! We have done few more rounds of TDD and ensured that our code works for sure, and we can sleep better at night knowing that it works as per intended.

Showing code coverage

Now it’s time to actually see how well we cover the code! Let’s use the nyc module we have installed earlier.

Add the test and coverage scripts

What it does is that it will generate both text and HTML reports, which gives you a more visual status of your code coverage!

Run the coverage npm run coverage

Output in the terminal

Because we specified HTML reported, we also get a coverage folder!

Coverage/index.html

From the coverage, we can see that we have about 87% code coverage! Which honestly is pretty good.

Closing thoughts

With test driven development, we can achieve high code quality, with high code coverage. This ensures that our app is always behaving as per expectation! No more manual testing or no more code breaking by doing some refactoring.

Even if you do not want to follow TDD, it is still good to write tests after you have implemented some functionalities. This way you are sure that any additional changes / refactoring, do not break the existing behavior of your app.

If you weren’t able to follow it exactly, you can view the full source code for this tutorial here.

Have fun coding!