Part 4. Node.js + Express + TypeScript: Unit Tests with Jest

It is not possible to develop an application efficiently without Unit Tests. This statement is more than the truth about HTTP API server. Literally speaking, I never run the server when I develop new functionality, but develop Unit Tests together with the code, and run Unit Tests to verify the code, step by step until I finish the functionality.

In previous parts of the tutorial, we developed a Node.js+Express+Open API App with GET /hello and GET /goodbye requests. If you start from this part, you can get it by:

$ git clone https://github.com/losikov/api-example.git
$ cd api-example
$ git checkout tags/v3.3.0
$ yarn install

Initial Setup

$ yarn add -D jest @types/jest ts-jest
$ yarn ts-jest config:init

To make jest tests files to see @exmpl scope, update just created jest.config.js and add moduleNameMapper property:

Now, we can run our tests with yarn jest command, but let’s add a new script to package.json for convenience:

"scripts": {
...
+ "test:unit": "ENV_FILE=./config/.env.test jest"
},

As you see, we pass it test env file. We don’t add any variables to it yet. Just create an empty file:

$ touch config/.env.test

Now, we can run our tests with yarn test:unit command. Let’s move on to our first unit test and try to run it.

Simple Test

Analyze the content. Pay attention to line 3,4 and 9:

auth/it should resolve with true and valid userId for hardcoded token
auth/it should resolve with false for invalid token

Keep the naming clear to you and others. You can use it() or test(), and use the one which fits your naming style. To organize tests you can use multiple describe() levels.

In line 5 and 10, we perform an action, and on line 6 and 11 we validate the actions’ result with jest expect function. we use one of the methods toEqual. Refer to the documentation to find the method you need for a value validation.

Run yarn test:unit to see the result:

HTTP Endpoint Unit Tests

$ yarn add -D supertest @types/supertest

Create src/api/controllers/__tests__ folder and greeting.ts file in it with the following content:

We verify GET /hello with an empty params list, with a name value, and with an empty name value. Let’s review the last test logic with an empty name param. We create our server in line 9 in beforeAll function which is called once before all tests. We create our get request (line 38 -39), and expect to get json Content-Type in line 40, 400 status code in line 41. Finally, we verify response body which should have an error description in 44. end() method is async, that’s why we call done() in line 49 which we got as an argument in line 37. If one of expect methods fails, it will interrupt the entire test and reports the error.

We can run a single test file to check the result passing the file name as an argument to yarn test:unit:

$ yarn test:unit src/api/controllers/__tests__/greeting.ts

To complete our greeting controller unit tests append one more describe function to src/api/controllers/greeting.ts which will test GET /goodbye request:

It is similar to GET /hello tests we did before, and the only difference, that we add an Authorization header field to our requests in lines 5 and 18. In the last test, we verify the behavior if the Authorization header field is omitted.

You can run the test for the file again on your own to see the result, and also, change the expected values to the wrong ones to see the error output.

To run all unit test, run yarn test:unit:

If you want to see individual tests results with test suite hierarchy when you run all tests, append a --verbose flag:

Code Coverage

It is obvious, that express_dev_logger.ts and logger.ts should be excluded from the results table as they divert attention from the files we want to test and track. To exclude the file completely from the coverage, add the following to the header of each file, to express_dev_logger.ts and logger.ts:

/* istanbul ignore file */
... imports ...

server.ts has logger initialization which also can be excluded from the results, as we don’t test the logger and its output. Make the following modification to exclude the blocks:

If we run yarn test:unit --coverage now, we won’t be destructed by the red output we are not interested in:

Mocking

It creates the endpoint on line 12, and make a GET /goodbye request in lines 18–20. As it requires authorization, it will call controllers/user/auth method which calls services/user/auth method. But, we mock services/auth in line 8 of the test above, and make the mock to reject with an error in line 17. As a result, a catch block of controllers.user.auth function is called, and it returns 500 with an error. We verify the result in line 20, and 23. If we check the coverage now, we’ll get:

We forced our code by mocking user service (auth fucntion) to get to the catch block, line # 20.

You can download the project sources from git https://github.com/losikov/api-example. Git commit history and tags are organized based on the parts.

In the next part of the tutorial, we’ll learn how to run MongoDB using docker, how to work in Node.js app with MongoDB using mongoose, and how to develop new REST APIs requiring MongoDB for our Node.js app only with Unit Tests.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store