DEV Community

Cover image for Unit Testing in NodeJS with Express & TypeScript || Part One: Environment Setup
Abeinemukama Vicent
Abeinemukama Vicent

Posted on

Unit Testing in NodeJS with Express & TypeScript || Part One: Environment Setup

Node.js is a cross-platform, open-source JavaScript run time environment that can run on Windows, Linux, Unix, macOS, and other operating systems. It runs on the V8 JavaScript engine, and executes JavaScript code outside a web browser.
On the other hand, ExpressJS is a web application backend framework for building restful apis with NodeJS.
TypeScript is a free and open-source high-level programming language developed by Microsoft that adds static typing with optional type annotations to JavaScript. In this guide, we will build an express api and add type checking using TypeScript and use it to learn unit testing while building restful apis with NodeJS.

This part is an intergral part of a 3 part series and a complete guide will involve environment setup, connecting an external database like MongoDB and writing database models and writing unit tests for crud operations(get, post, delete, put).
This first part will only discuss the environment setup and overall folder structure.

What Exactly is Unit Testing?

Unit testing is a software development process in which the smallest testable parts of an application, called units, are individually scrutinized for proper operation. In other wards, each part of the application is tested in isolation before merging to the main repository.
In the fast-paced realm of software development, ensuring the reliability of your code is paramount. Unit testing plays a crucial role in maintaining code quality, catching bugs early, and facilitating smoother collaboration within a team. In this multipart series, I'll guide you through the process of setting up a robust unit testing environment for your Node.js application built with Express and TypeScript using Jest and Supertest.
Jest is a delightful JavaScript Testing Framework with a focus on simplicity. It works with projects using: Babel, TypeScript, Node, React, Angular, Vue and other JavaScript technologies. We will also use Supertest for http assertions, forming the backbone of our testing suite. It is a NodeJS library, and enables developers and testers to write automated tests for routes and endpoints and can be used as a standalone library or with testing frameworks like Jest and provides a high-level abstraction to test HTTP requests.

Project Setup

Before diving into unit testing, it's essential to have a well-organized project structure. Whether you're starting a new project or integrating testing into an existing one, maintaining a clear separation of concerns will make your life easier in the long run. Consider structuring your project with directories for source code, tests, and configuration files. This foundational step ensures that your codebase is ready to embrace a testing mindset.

Steps:

Create a new folder in your desired location and name it unit_testing_api_guide.
With the folder open in your favourite code editor, open terminal and run the following command to initialise a NodeJS project:



npm init -y


Enter fullscreen mode Exit fullscreen mode

After initialising the project, install the following dependencies with the following command:



npm install express cors helmet


Enter fullscreen mode Exit fullscreen mode

express is NodeJS framework we are using for routing, cors helps us define a way for accessing our api on different domains whereas helmet is collection of middleware functions that set various HTTP headers to enhance the security of your Node. I prefer starting with it in any NodeJS project because it starts by hiding vulnerable headers like 'X-Powered-By' header which tells about the technology youre using giving attackers a chance to concentrate on its vulnerabilities to penetrate your API.

All the dependencies for unit testing are only used in development so we will install them as dev dependencies(with a -D or --save-dev flag) along with other development dependencies like typescript after which I will explain.

On the same terminal, run the following command:



npm install -D typescript ts-node nodemon jest ts-jest @types/jest supertest @types/supertest @types/cors @types/express


Enter fullscreen mode Exit fullscreen mode

Let me explain:
TypeScript is meant to help us in type checking our code and and transpiles to JavaScript. ts-node is a TypeScript execution engine and REPL for Node.js and will will help us transform TypeScript into JavaScript, enabling direct execution of TypeScript on Node.js without precompiling.
nodemon is meant to help us restart the server in case of changes while in development. Also note that, in newer versions of NodeJS, nodemon is being phased out and you will find yourself using --watch flag instead. Nevertheless, we will use it in this guide to restart our server automatically in case need arises.
jest is the testing framework we are using and @types/jest for adding TypeScript definitions for jest. On the other hand, ts-jest is a Jest transformer with source map support and lets you use Jest to test projects written in TypeScript and supports all features of TypeScript including type-checking.
supertest is for providing high level of abstraction to test http requests and @types/supertest for providing TypeScript definitions for it.
@types/cors and @types/express are also for providing type definitions for cors and express respectively.

These are the dependencies we need for now and note that we may require other dependencies in the next parts of this guide with their type definitions for TypeScript but we will install them when need arises.

Project Folder Structure

Here is our overall project folder structure:

Image description

I find this level of separation of concerns appropriate in most of my NodeJS projects and we will be sticking to it in this guide.

Lets create our project step by step and remember our main emphasis is on testing directories and files. However, since we are going to learn how to write unit tests for each line of code from controllers, to routes and code in other directories, we need to understand how other folders are created, what they contain and why separate them.

First, create a new folder in the root directory and call it src. Inside src, create all folders as shown in our folder structure. Worry not, we will understand what each folder consists of step by step.
However, in this first part of the guide, we will write all our code inside the index file, index.ts as its all about environment setup and will go through most of the folders in the coming parts.
Also, create a file named index.ts inside src and place the following code:



import express from "express";
import cors from "cors";
import helmet from "helmet";
const app = express();

// USE HELMET AND CORS MIDDLEWARES
app.use(
  cors({
    origin: ["*"], // Comma separated list of your urls to access your api. * means allow everything
    credentials: true, // Allow cookies to be sent with requests
  })
);
app.use(helmet());

app.use(express.json());

app.get("/", async (req: express.Request, res: express.Response) => {
  try {
    res.send(
      "Welcome to unit testing guide for nodejs, typescript and express!"
    );
  } catch (err) {
    console.log(err);
  }
});

// Start backend server
const PORT = process.env.PORT || 8800;
app.listen(PORT, () => {
  console.log(`Backend server is running at port ${PORT}`);
});

export default app;



Enter fullscreen mode Exit fullscreen mode

Before running our code to start writing unit tests, lets first make some configurations:
Create a file named tsconfig.json in the root directory and add the following configuration:



{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "target": "ES2020",
    "baseUrl": "src",
    "noImplicitAny": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "outDir": "dist"
  },
  "include": ["src/**/*"]
}



Enter fullscreen mode Exit fullscreen mode

Create another file named nodemon.json in the root directory and place the following code:



{
  "watch": ["src"],
  "ext": ".ts,.js",
  "exec": "ts-node ./src/index"
}


Enter fullscreen mode Exit fullscreen mode

Inside package.json, add the following scripts in the scripts section:



"scripts": {
    "build": "tsc",
    "start": "npm run build && node dist/src/index.js",
    "dev": "nodemon",
    "test": "jest --watchAll  --detectOpenHandles"
  },


Enter fullscreen mode Exit fullscreen mode

The build command helps us transpile TypeScript to JavaScript in production where npm start command is used to start or restart our server. The dist folder is the destination for our resultant JavaScript code as specified in tsconfig.json. In development, nodemon restarts our server automatically without a need for transpilation first.

Also, add the line:



"module": "module",


Enter fullscreen mode Exit fullscreen mode

to your package.json to specify that we are using EsModules not NodeJS's default CommonJS pattern.

All set, now open the terminal and traverse to the location of your home directory and run:



npm run dev


Enter fullscreen mode Exit fullscreen mode

to run the project in development mode.
Note that the command:nodemon is executed and the result should be as shown below:

Image description

Jest Setup

Create a new file in the home directory and name it jest.config.js and place the following code:



module.exports = {
  preset: "ts-jest",
  testEnvironment: "node",
  testMatch: ["**/**/*.test.ts"],
  verbose: true,
  forceExit: false,
};


Enter fullscreen mode Exit fullscreen mode

This is a basic configuration for jest and we will be adding some additional config code when need arises in the next parts of this series.

Writing Sample Unit Tests

To confirm that our project is setup correctly for unit testing, lets write some tests.
Inside the tests folder, create a new file named app.test.ts and place the following code:



import app from "../index";
import request from "supertest";

describe("GET /", () => {
  it('responds with "Welcome to unit testing guide for nodejs, typescript and express!', async () => {
    const response = await request(app).get("/");
    expect(response.status).toBe(200);
    expect(response.text).toBe(
      "Welcome to unit testing guide for nodejs, typescript and express!"
    );
  });
});



Enter fullscreen mode Exit fullscreen mode

Let me explain:
We started by importing our Express app (app) and the supertest library, which is used for making HTTP requests in our tests.
We then have a simple Jest test case, It describes and tests the behavior of your Express app when a GET request is made to the root ("/") endpoint. It expects the HTTP response status to be 200 and the response text to match the expected message.

On your terminal, run the following command:



npm run test


Enter fullscreen mode Exit fullscreen mode

or just



npm test


Enter fullscreen mode Exit fullscreen mode

and you should see the following output:

Image description

Congulatulations, you just setup a unit testing environment for a NodeJS, Express app using TypeScript.

Interested in the source code? Checkout the repository for the article here
If you liked the article, consider following me on Twitter
Stay tuned for the next 2 parts in this series and furture articles.

Top comments (1)

Collapse
 
edaos profile image
O S

Thanks for the article! Just a quick note: the test you showed is actually an integration test not a unit test. To do it you can abstract the route hanlder function and then test it as a unit of code.