Show some love for Mocha api tests! Testing #2
Introduction
This is a continuation to the first part which you can find here. In this part we are going to dive deeper into actual api tests. I will show you some adapted Design patterns in Javascript and we will do full test without any mocks or stubs.
Let’s set up
First we have to set up a single test and Mocha script to run it. We can do this by creating any test in tests/
directory following pattern tests/**/*.test.js
. The command to start tests is very simple: mocha tests/**/*.test.js —-reporter dot
.
Example test consists of single describe
block to define tests suite and one case — it
block.
Now I will create two modules in my tests/
directory — util.js
and create.js
.
Util module is responsible for all the tests infrastructure, exporting everything from other files, gathering models and things like that. For now it will clean up database before each test and configure mongoConnectionString
.
Create module is basically an adapted factory design pattern in Javascript. Don’t be afraid, I’m not going to use classes and fancy OOP things. I will create two functions for each database model in our system. One will be responsible for creating a DTO of an entity (Data Transfer Object) and another one for inserting actual item to the database. This will come in handy when we want to pre-set database with existing entities or we want to create something via POST api endpoints that hadn’t existed in the database.
Don’t be lazy and define all the fields explicitly. Also a nice practice is to use unique identifier for each string field suffixed with a field name. That’s just something what helps me debug my tests.
The tests
I like to start with defining main test cases and blocks for each api endpoint.
As you can see there two types of tests — failing path and happy path. I’m testing all kinds of scenarios, especially the bad ones. Usually each api endpoint would have authentication and authorisation checks. For example not sending a token, sending invalid token, not having required permissions etc. Sometimes test cases might include throttling — when you are expecting to receive 429 Too Many Requests error and things like that. This example is very basic and simple — testing interactions with the database, some validation bits and restful api guidelines checks.
How are we going to call the api you ask? It’s easy — we are going to use supertest
. Of course we have to install first with npm i -D supertest
.
Now let’s start with GET /api/items endpoint.
That was easy, right? Basically I’m calling my express
application with supertest
function request
and expect some results. I always like to check headers like Content-Type
and the ones I have set myself — X-Total-Count
. And all I have to do in the end — expect an empty array — the actual body. Now let’s try creating some stuff and calling the api again.
I introduced assertion library — NodeJs
native assert
. I don’t see any good reasons to use libraries like chai
for simple assertions you can achieve by using what the environment provides you.
So I’m calling the api once again but with pre-inserted items in the database and I expect them to come back. I also introduced mapToTestDTO
function that I imagine like an adapter pattern — I’m adapting both response and expected resources to be the same structure. Let’s finish with the GET /api/items tests by coding the last test case.
And that’s it. We set query to return only one item, but we still expect X-Total-Count
to be 3 as it’s not affected by query.
I’m not going to concentrate to all tests as much as I did to this one, but I will still show you all the implementations and give some insights. Let’s do GET /api/items/:id endpoint next.
So this are very self-explanatory. First test suite expects validation error when invalid Mongo id is given and it gets one.
Second test expects NotFound error when the database is empty. What can you return when there is nothing in it?
Third test again expects NotFound error even when the database is not empty but the random valid id is given.
The last test just expects a single item returned.
Now we have to create something. We are going to test POST /api/items endpoint. This will have some additional validation logic.
First test checks what happens when I send empty body — I get an error accordingly.
Second test tries to send a bunch of nonsense and once again — responds accordingly to the request.
Third one has some additional logic. After the item is created we are taking Location header value and extracting the id. The header looks like the following /api/items/:id
. With this id I’m making a request to the database and validate that new Item was created with my body payload values.
Time for some updating. You won’t see anything new, just instead of creating new item after a lot of validation passes we are updating already existing one.
You have seen most of these already. NotFound and id validation is basically taken from GET /api/items/:id tests, body validation is a copy of POST /api/items and only the last one — update has something a bit different but it’s still very similar to POST endpoint. Don’t be afraid to reuse your stuff, just don’t overcomplicate, sometimes a nice copy and paste is better than complex design pattern.
And the last one — deletion. It basically has one prerequisite — in order to delete an item it has to exist in the database. Let’s try.
And that’s it! Once we have validated id integrity and item existence — we can expect for it to be removed.
Final touches
Try configuring Mocha test script for watch or development mode. It’s super fast and it reacts to all files changes. You can even rerun it with simple save command or type rs
in the terminal. The beauty of it is that in the watch mode you don’t have to restart the whole Mocha runtime environment, it keeps running all the time and just waits for the command to start actual test cases.
Utilise the power of .only
and .skip
keywords. You can add these on any describe
and/or it
blocks. Only
will only run these tests you selected and skip
… well it will obviously skip them!
There are different test reporters as well, I just like to use --reporter dot
. Here is an example of other options:
Also there are many different reporters libraries, for example: mocha-simple-html-reporter
, mocha-teamcity-reporter
etc. You can install them just like any other library and use with the Mocha --reporter
flag.
Final words
Always test your applications. I’m not against unit tests at all and I think all tests deliver additional value be it unit, integration, api or e2e. As a primarily back end developer myself, I value API tests the most. They are also very nicely implemented with a common and well known TDD framework. In addition, well designed api tests can work as some sort of documentation for other developers in your team.
We can make api tests very fast with Mocha, we can avoid unnecessary mocks, we can test full and actual back end flow. This allows you not only to test the interactions with the database but all sorts of things, for example security scans, throttling, logging, audit and so on. Don’t be afraid of tests and show some love for Mocha api tests!