Testing is hard and this is why you should do it: learn how to test a simple Koa 2 API with Mocha, Chai, and Chai HTTP. Code included!
Building something with Javascript is relatively easy. Take a look here and you’ll see an uninterrupted stream of articles and tutorials.
But… how many of them covers Test Driven Development with Javascript? Few. Mine included. Testing is hard and more often than not I find myself writing code without even having a test case.
Intrigued by Koa 2, I’ve started to play with it lately to build an API. And I liked it, especially when you start to see all those green marks after typing npm test. So, what follows is an introduction to building a RESTful API with Koa 2, Mocha and Chai, by following the TDD principles.
Koa 2, Mocha, and Chai in action
The concept is simple: we’ll build a simple API for displaying and storing a collection of blog articles, code and tests included!
The following guide provides some basic examples. It does not cover every possible use case but will give you a good starting point either way.
Also, keep in mind that the code in this tutorial is not suitable for production. Use it at your own risk. Its purpose is to show you the basics. Don’t copy/paste the examples without having a solid understanding about what the code is supposed to do.
In order to follow along with this tutorial you should have a basic understanding of Javascript, Node.js, and ES6.
The latest version of Node.js is available from the official website.
If you use Linux, you should absolutely check out Nodesource.
Also, make sure to have at least Node v7.6.0 or higher. In the following examples I’ll make large use of the async/await pattern.
To start the project, create an empty directory named koa-tdd-api:
mkdir koa-tdd-api
then move inside the newly created directory:
cd koa-tdd-api
and initialize package.json by running:
npm init
We won’t publish any module to NPM so you can safely accept the default choices and just move on.
We also need to create some directory structure. In your terminal type:
mkdir -p server/{routes,db,controllers} test
and hit enter.
That’s a boring task but someone has to do it: setting up the database and all the likes. Our tools of choice will be:
Before doing anything else install the Sqlite3 adapter with:
npm i [email protected] --save-dev
The reason why I picked version 3.1.8 is because of this issue. I hope it will fixed soon.
Right after that add Knex with:
npm i knex
Knex comes included with a command line tool for managing both migrations and seeding. The Knex CLI can also be used to initialize the Knex configuration file. Run in your terminal:
node_modules/knex/bin/cli.js init
and you should see the following output:
Created ./knexfile.js
This file will hold the database credentials for every environment. You can also specify where your migrations and seeds should be generated. Open up knexfile.js and update the configuration by replacing the defaults with the following:
<a href="https://medium.com/media/d95fb78b66e997043717c906f94e0d5f/href">https://medium.com/media/d95fb78b66e997043717c906f94e0d5f/href</a>
Of course you’re free to load in other environments such as “dev” or “prod”, anyway for the scope of this tutorial I’ll stick just with a “test” environment.
At this point you can safely generate your first migration with:
node_modules/knex/bin/cli.js migrate:make articles --env test
A new migration will be created inside server/db/migrations/. Open up the newly created file and update the defaults with the following code:
<a href="https://medium.com/media/2a592fde391d40273774b656c994896c/href">https://medium.com/media/2a592fde391d40273774b656c994896c/href</a>
The above code defines a new table named articles. Every article will be composed of a title, a body and some tags.
Save and close the file, then commit the migration with:
node_modules/knex/bin/cli.js migrate:latest --env test
A new file, test.sqlite3 will appear inside your project directory. It’s the test database. Eager to see what’s inside? You can browse it with sqlitebrowser even if it’ll be most likely empty right now. Time to load some data!
Right now the database is empty. We need some kind of data which will be used by the testing suite during its execution. Knex makes easy to create fake data by making use of seeds.
NOTE: Seeding is not meant to be used during testing. That’s a job for fixtures and factories. Seeding is mostly used for populating an application with some data needed to allow the software to be usable as soon it starts. Knex does not have a fixtures feature, therefore we’ll use seeding either way
Generate the seed file by running:
node_modules/knex/bin/cli.js seed:make articles --env test
and a new seed will appear inside server/db/seeds/articles.js. Open up the file and add some fake articles inside the insert() call:
<a href="https://medium.com/media/af4ad2192f7a30a01a0c65834cab4959/href">https://medium.com/media/af4ad2192f7a30a01a0c65834cab4959/href</a>
Now that everything is set let’s start with the serious stuff: setting up the testing suite.
In brief, Test Driven Development is a programming style based on the repetition of a set of steps:
Test Driven Development can be easily implemented within Javascript. We will make use of three renowned libraries:
Mocha: it’s a Javascript testing framework with a lot of features. With Mocha you can organize and automate your tests easily
Chai: it’s an assertion library for Javascript. An assertion library is nothing more than a tool for verifying that your code produces the intended result. If you’re new to testing I highly recommend reading the documentation before getting started
Chai HTTP: it’s a plugin for Chai. It makes easy to test APIs and web servers
As usual, install the dependencies with:
npm i mocha chai chai-http --save-dev
and you’re ready to go.
Update the script section inside package.json. The test command should point to mocha:
"scripts": {
"test": "mocha"
}
Following the TDD principles, let’s start with a simple test case. In this first test we want to make sure that our server will respond to every HTTP requests against the /api/v1/articles endpoint.
Create a new file named articles.routes.test.js inside the test directory:
<a href="https://medium.com/media/2b55b651f542c687427c9dcd2884da40/href">https://medium.com/media/2b55b651f542c687427c9dcd2884da40/href</a>
The first two sections of the file serve for the purpose of configuring Knex and requiring the assertion libraries: Chai and its Chai Http plugin.
Next there are two calls to Knex: the first call rollbacks, commits the migration and populates the test database before each test. The second call to Knex instead makes sure that the test database gets cleaned at the end of every test case.
Right after that comes the first test, starting with the describe block. Both describe and it are Mocha functions.
The describe block helps you organizing a group of similar tests, against a specific endpoint for example.
The it block on the other hand is the actual test being declared.
By looking at the above code it should be clear also how Chai makes possible to spawn the server and subsequently making a get request:
//..
chai
.request(server)
.get(`${PATH}`)
// ..
So what’s the test all about? Speaking of HTTP servers, you may want to check whether a given route responds correctly or not by:
Checking if no errors exists:
// ...
should.not.exist(err);
//...
Checking if the server responds with the expected status code:
res.status.should.eql(200);
Checking if the server sends back valid JSON:
// ...
res.type.should.eql("application/json");
//...
Checking if the JSON response contains the expected output:
In the example below I’m asserting that the response body contains an array named data, which should have a length equal to 2:
// ...
res.body.data.length.should.eql(2);
//...
and I also want to make sure that the first element of the data array contains the keys “title”, “id”, “body”, and “tags”:
// ...
res.body.data[0].should.include.keys("id", "title", "body", "tags");
//...
and so on.
It’s worth noting that Chai supports three different assertions styles: the Should style, the Expect style and the Assert style. Picking one over another it’s a matter of personal preferences. Personally I feel the Should style more natural.
With the first test case in place, it’s time to make it fail. To do so, create a new file named index.js inside the server/ directory and put a simple console.log() call inside it:
<a href="https://medium.com/media/ea405f52dd7b6c58cb1565ab4f5b739c/href">https://medium.com/media/ea405f52dd7b6c58cb1565ab4f5b739c/href</a>
Ready? Run the test suite with:
npm test
and watch the test fail:
TypeError: app.address is not a function
To make the test pass we should move to the green phase, or rather: write just enough code to make the test pass.
Koa is a web framework for Node.js.
How many frameworks for Node.js exist as of today? I’ve lost the count: picking one over another is a tough choice. Lately I’ve been enjoying Koa amongst the others for two simple reasons:
and there is a lot more. I suggest reading the documentation to discover what Koa has to offer. One of its features is the context object for instance.
You will see a lot of references to the ctx object in the upcoming examples. ctx, or context, is an object containing both the Node.js request and response objects. It is specific to Koa and provides a lot of useful methods.
In its basic form an API would be composed of three key components, glued together.
A server: it’s the component responsible for listening on a given network port, forwarding all the request to the appropriate route
A set of routes, ie the components responsible for handling the incoming requests. This route for example:
router.get("/api/v1/articles", articlesController.index);
will handle all the requests for GET /api/v1/articles by asking the articlesController.index function to retrieve the appropriate data from the database. You should pay attention to not put too many business logic inside your routes.
For example you may be tempted to write:
// BAD
router.get("/api/v1/articles", async ctx => {
try {
const articles = await knex("articles").select();
ctx.body = {
data: articles
};
} catch (error) {
console.error(error);
}
});
The above code will work fine but it’s not immediately understandable as:
// GOD
router.get("/api/v1/articles", articlesController.index);
Finally we have the controller: to put it simply, the controller is responsible for intepreting the request, fetching the requested data and sending an appropriate output back to the user.
Besides some slightly variations between every web framework, those listed above are some of the main building blocks of the MVC pattern. The key difference here is that with Rails, Laravel, Phoenix and co. you get some sort of organization for free. With Javascript and Node.js you won’t have any guidance about the project structure, unless you go with Trails or something similar.
With this basic theory in place let’s start by creating the server. Our goal is still to make the previous test green.
Install Koa with:
npm i koa
open up server/index.js again, remove the existing code and replace it with the following:
<a href="https://medium.com/media/cc1aac206978f48faf1307fa931155bf/href">https://medium.com/media/cc1aac206978f48faf1307fa931155bf/href</a>
save and close the file, then move on to setting up the routes.
Koa does not include a router by default. Routing functionalities are provided inside the koa-router package:
npm i koa-router
The first route we’ll going to create is the endpoint for GET /api/v1/articles.
Create a new file named articles.routes.js inside the server/routes directory:
<a href="https://medium.com/media/6fd801b47a71fb6e287af0331177a3ef/href">https://medium.com/media/6fd801b47a71fb6e287af0331177a3ef/href</a>
Did you noticed how I’m requiring the articlesController inside the article route? Unlike a full MVC framework, Koa does not enforce a strict structure for your project. How the application gets organized is completely up to you so we’d better follow some conventions.
A controller named articlesController should exists and a corresponding action named index should be defined inside the controller as well. The action can be a basic Javascript function: it should fetch the resources from the database and return something meaningful to the user.
Start by creating the controller alongside the action inside server/controllers/articlesController.js:
<a href="https://medium.com/media/90e2c7514fce2d9c7887794178f936b4/href">https://medium.com/media/90e2c7514fce2d9c7887794178f936b4/href</a>
Pay attention to the above async arrow function which takes ctx as a parameter and:
Save and close the file: at this point we have a basic API with a single route, waiting to be tested.
Run the test suite again with:
npm test
and watch your first test pass:
Testing GET /api/v1/articles
Let’s move on by writing another failing test for the next endpoint: GET /api/v1/articles/:id. This route is expected to return a single resource from the database.
Update test/articles.routes.test.js by adding the code below:
<a href="https://medium.com/media/6b928b2e6035c844300338178dbfff52/href">https://medium.com/media/6b928b2e6035c844300338178dbfff52/href</a>
In the above code we’re basically testing the following behaviours:
We’re also expecting the correct status codes to be returned from the API.
Run the test suite with:
npm test
and watch it fail. To make the test pass you should update the routes, again.
Open up server/routes/articles.routes.js and add:
<a href="https://medium.com/media/692d277151519ba1437b09f140b47123/href">https://medium.com/media/692d277151519ba1437b09f140b47123/href</a>
update the controller as well by adding the show action inside server/controllers/articlesController.js:
<a href="https://medium.com/media/94e869cf81afd321edf5c773ed0691a3/href">https://medium.com/media/94e869cf81afd321edf5c773ed0691a3/href</a>
Again, look at the above async arrow function which takes ctx as a parameter and:
Run the test suite again with:
npm test
and watch the test pass:
Testing GET /api/v1/articles/:id
Our last test will check the behaviour of the POST /api/v1/articles/ endpoint. The route is expected to create a new resource inside the database by starting from the request body.
Koa doesn’t know how to handle the body by itself, thus we must install koa-bodyparser:
npm i koa-bodyparser
update server/index.js as well in order to use the new middleware:
<a href="https://medium.com/media/1915db5ecafdb7187fe75715c6669ebd/href">https://medium.com/media/1915db5ecafdb7187fe75715c6669ebd/href</a>
Update the tests as well by adding the code below inside test/articles.routes.test.js:
<a href="https://medium.com/media/a99173695029feb13bc15b6f5abe90a1/href">https://medium.com/media/a99173695029feb13bc15b6f5abe90a1/href</a>
In the above code we’re testing if:
We’re also expecting the appropriate status code to be returned from the API:
201 Created: This status code should be returned whenever a new resource has been created. See 201
409 Conflict: This status code should be returned whenever a new resource couldn’t be created because the same one is already present inside the database. See 409
Run the test suite when you’re ready:
npm test
and watch it fail.
Guess what? To make the test pass you must add the corresponding route.
Open up server/routes/articles.routes.js and add:
<a href="https://medium.com/media/0921874688848f1797d1d932f4a2051a/href">https://medium.com/media/0921874688848f1797d1d932f4a2051a/href</a>
update the controller as well by adding the create action inside server/controllers/articlesController.js:
<a href="https://medium.com/media/e8024140966c2465538bb8b04b4613cc/href">https://medium.com/media/e8024140966c2465538bb8b04b4613cc/href</a>
The above code has another async arrow function which takes ctx as a parameter and:
Now run the test suite again:
npm test
and watch all the tests pass:
Testing POST /api/v1/articles
The pattern should be clear now: write a failing test, add just enough code to make it pass, then refactor. By doing so you will not only be more confident and satisfied with your code, but more importantly, you’ll ship only the necessary logic for making your app work, nothing more, nothing less.
The delete route next to the corresponding controller’s action would be a nice addition to this simple API: that’s left as an exercise for you.
The code for koa-tdd-api is available on Github: koa-tdd-api
A more complex example with basic authentication is available at faq-app-api
Have you spotted any error in my code? Any suggestion? I would love to ear your input in the comments below!
Testing is hard and this is why you should practice it: it must become an habit.
It will require time and discipline to incorporate TDD into your workflow, especially if you’re new to Web Development. In fact I encourage you to begin with TDD as soon as possible: the benefits will be invaluable.
If you’re absolutely new to TDD I suggest starting with Why learning TDD is hard, and what to do about it.
After you get a grasp of how TDD works, start immediately by creating tests: as a rule of thumb, next time you’re about to write some code stop it and write a test case first. Make it fail and only then you can start adding the appropriate code to make the test pass.
Once you’re confortable with the basics, go learn about Unit and Integration Testing too!
Thanks for reading! See you next time!
Originally published on valentinog.com/blog on September 3, 2017
Web Developer & IT Consultant, with over 10 year of experience, I’m here to help you developing your next idea. Got something to do? Let’s get in touch