As a professional software developer dedicated to Node.js RESTful APIs, I have come to the following conclusion:
Developers are not paid to write code.
They are paid to deliver tech
solutions.
And these solutions should be...
- Concrete and robust
- Have high availability no matter the load
- Reliable
- Secure
- Cost-effective
- Maintainable
Developers should also be able to provide evidence that their solutions match the criteria mentioned above. They should also be able to detect and fix easily and fast any bug or issue that may occur.
And that's where Unit testing comes in
Definition
Unit Testing is a level of software testing where individual units/ components of a software are tested. The purpose is to validate that each unit of the software performs as designed.
Source: softwaretestingfundamentals.com
But which are the units in an API?
The units in an API consist of:
- API requests
- HTTP method (i.e GET, POST, PUT etc.) API endpoint (i.e /v1/posts)
- Request parameters
- Request headers
- Request Body • Models
- Properties / fields
- Model methods
Learning by example: An example API
For the purposes of this article, we will use an example API for a classic book Library (yes, the original one where you can borrow books, study, etc.)
The API will be composed of the following elements:
-
Entities / Models
- Books
- Users
-
Endpoints
- GET /users
- POST /user
- GET /books
- POST /book
The endpoints have the form shown in the following screenshots:
We use faker.js to generate the dummy data that the API will use.
So far so good. I think it is crystal what each endpoint does and the form of data that it responds with.
An example response for the GET /users endpoint looks like this:
But what do we really want to test?
By writing unit tests for an API, we try to answer questions like these:
- Does GET /users always responds with an array of user objects?
- Does POST /book always responds with the book object submitted?
- Does POST /user responds with the right error code when one or more required fields are missing?
- Does POST /user responds with the right error code when email does not have the correct format?
Of course, there are many more questions that we may want to answer to be sure that our API works as expected but for our example, those are some important ones.
Let’s grab a cup of coffee (or tea ?)
The 2 main libraries we use to write unit tests for Node.JS applications are Mocha which is the main unit testing framework and Chai which is the assertion library. Chai provides the functions that make the checks we want to perform a lot easier.
i.e
response.should.be.a('string');
response.should.not.have.property(‘phone’);
Chai library has 4 main interfaces that do the same thing with
different syntax:
- should
- assert
- expect
i.e the following 3 lines perform exactly the same test.
email.should.be.a(‘string’)
expect(email).to.be.a(‘string’)
assert.typeOf(email,’string’)
A look in the package.json
file of our project
In order to run tests with the default npm test command
we should add the following value in the scripts
key of our package.json
file.
"scripts": {
"test": "nyc mocha --timeout 10000"
}
We set the timeout for each test case (a test case performs an API call) to 10K ms (or 10s).
The anatomy of a test
As you can see a test is composed of
- The dependencies (common for many test cases)
- A name and a description
- The API call
- The actual tests (assertions)
- The callback that notifies mocha library that the test has completed.
Coverage reports and nyc
nyc is a tool that reports how much of the total code is covered by the tests we have written. It also reports all the uncovered lines so to know where to look and what tests to write.
A coverage report after the completion of the tests looks like this:
Some good practices regarding unit tests
It is a good practice to save the different payloads we use to test POST endpoints in separate .txt or .json files.
We should also create different test declarations for different things /
functions we want to check.We should also try to write tests in order to form different “scenarios”.
i.e The DB is initially empty, so we POST a new user, then the user created POSTs a new book, then we DELETE the book and then the user etc.We should also write tests to check error codes and errors. Bugs and issues may be hidden in the validation logic.
We should also write tests checking access level if our API has different user types with different access levels
Finally, we should try to reach the higher coverage we can. However, we should always keep in mind that it is impossible to reach 100%.
That's all folks!
I hope you enjoyed it and that it will help you to write unit tests for your Node.JS API in the future.
Top comments (1)
Cool article! It would be helpful if you can provide a Github link to the code examples.