Node Testing: Jest + Supertest

Testing Node.js, Express with Supertest & Jest testing framework.

Passing tests

With a multitude of testing framework available for JavaScript and Node.js in specific, it can quickly become overwhelming when deciding which to choose. For the seasoned, the choices usually narrows down to Mocha paired with assertion library like chai. Although Mocha can also be used for testing on the browser side, I have found having a uniform test framework across the Node.js and browser (React) environment to be more rewarding, and there’s no other choice that can rival Jest. The beauty of Jest is the out-of-the-box support on React and zero configuration almost elsewhere.

Now, first things first. Let’s create a minimal RESTful API that returns a cliché of todos. I will be using TypeScript and for simplicity, the todos will be stored on a plain file rather than a database. We will also use supertest for HTTP assertion. To follow along, clone the demo repo & switch to clean branch:-

git clone https://github.com/karanja-simon/node-jest-test.git
git checkout clean

Install tests dependencies

We will need to install jest, ts-jest and supertest:-

yarn add -D jest ts-jest supertest

Perhaps worth mentioning, ts-jest is a TypeScript preprocessor for jest that allows it to transpile TypeScript on the fly. In short, it allows you to test codebase written in TypeScript.

Since we are using typescript, let quickly add types:-

yarn add -D @types/jest @types/supertest

We also need to create a configuration that Jest is going to use when running our tests:-

jest --init

Based on your project, Jest will ask you a few questions and will create a basic configuration file with a short description for each option. Will also need to tell jest to use ts-jest. Finally, your config should look some thing like this:-

The tests

Of importance here is the todo.controller.ts that lives in src/api/controllers and our todo.service.ts in src/api/services. Since our controller is dependent on the service, lets first deal with the latter. Let’s see how our todo service looks like:-

This is a simple as any todo can get. With the spirit of unit testing, it’s clear that we need to test the three functions: allTodos(), todoById(id: number) and todoByStatus(status: string).

Let’s fire our favorite editor and open the todo.service.test.ts in src/api/services/__tests__ and add the following:-

As with any Jest test, we wrap our test cases in describe, then implement our first test case, line 11–15. Since our allTodos() returns a promise, we pass async keyword in front of the function we pass to it. This tells jest to wait for the completion of the asynchronous code before executing the next test. We get the todo on line 18, then do our assertions with expect. If we run our tests:-

yarn test

We should see a healthy test run. You can also pass coverage flag to see the test coverage.

Notice we have 100% coverage, which is always a good sign.

Now, to the second file we need to test. Open todo.controller.ts inside src/api/controllers. Let’s have a look:-

The code is straight forward. We have three controller functions that corresponds to our service functions. I have also used express-validator for validating our API requests. As usual, open the todo.controller.test.ts inside src/api/controllers/__tests__ and paste the following:-

This follows the same recipe as the tests we have covered above, but here we need a way to test our HTTP code. We need to simulate users hitting our GET end points. This is where supertest comes to play. We import request from supertest on line 1 and create our first GET request on line 14.

We can then assert the response from request. Running this, again we get a nice green pass with 100% coverage.

Gotchas

As you setup your tests and do your first run, you may get an error similar to this:-

This happens if you have not configured your transform property in jest configuration properly. Remember as I mentioned above, jest requires the .ts files to be transpiled to .js. So, ensure or check that transform on jest.config.ts is configured as:-

........
transform: {"^.+\\.(ts|tsx)$": "ts-jest"},
........

As you may have noticed, I have just covered R (Read = GET) of the CRUD (Create, Read, Update & Delete) possibilities of our API. The rest will follow the same approach, and is left as an exercise for the reader.

Keep Coding. ❤️. Shukran!