DEV Community

Petros Demetrakopoulos
Petros Demetrakopoulos

Posted on

Unit testing Node.JS APIs

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.

GET /users endpoint
GET /users

POST /user endpoint
POST /user

GET /books endpoint
GET /books

POST /book endpoint
POST /book

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:

Example response

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);
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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

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:
Coverage report

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)

Collapse
 
reach009 profile image
Reach Allen

Cool article! It would be helpful if you can provide a Github link to the code examples.