DEV Community

Hassan Sani
Hassan Sani

Posted on

How to Set Up Testing (TDD) for Typescript Nodejs API

In this article, we will see how to set up TDD for Nodejs Typescript API.

The benefits of writing tests

The purpose of a test case is to determine if different features within a system are performing as expected and to confirm that the system satisfies all related standards, guidelines and customer requirements. The process of writing a test case can also help reveal errors or defects within the system.
-- Kate Brush, What is a Test Case? - SearchSoftwareQuality

The above is the best way to describe tests.

Typescript

TypeScript is a strongly typed programming language that builds on JavaScript.

Node.js

Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine.

Jest

Jest is a JavaScript Testing Framework.

Prerequisites

Check if Nodejs is installed

node -v
Enter fullscreen mode Exit fullscreen mode

You should have the below output, it varies on the version you install

v14.18.1
Enter fullscreen mode Exit fullscreen mode

Start the project

We will start by initializing a new nodejs application. Create a folder for your project, let call it HelloWorld. In the directory open your terminal and run the code

npm init -y
Enter fullscreen mode Exit fullscreen mode

You are instructing Nodejs to initialize a new application and accept every question as default, this should create a package.json file in the directory.

Typescript is a superset of javascript, in this case, typescript still transpile to javascript before you can run and execute your code.

Dependencies

Let's add dependencies for the application.

  • Expressjs: A nodejs API framework.

To install Expressjs run

npm install --save express
Enter fullscreen mode Exit fullscreen mode

DevDependencies

Development dependencies, this is a typescript project we are required to install dependencies types as devDependencies to help nodejs with type definitions for the dependencies.

  • @types/express
  • @types/node: This helps with type definitions for Node.js
  • ts-node: It JIT transforms TypeScript into JavaScript, enabling you to directly execute TypeScript on Node.js without precompiling, we should run the tests without compiling our code into javascript first.
  • jest, jest-cli: Javascript testing framework to run tests
  • @types/jest
  • ts-jest: A Jest transformer with source map support that lets you use Jest to test projects written in TypeScript.
  • supertest: HTTP assertions to help our test make API calls of GET, POST, etc
  • @types/supertest
  • typescript: well, this is a Typescript project

Now let install these dependencies.

npm install --save-dev @types/express @types/node ts-node jest jest-cli @types/jest ts-jest supertest @types/supertest request @types/request typescript
Enter fullscreen mode Exit fullscreen mode

Configuration

Typescript

To set up the typescript project we need to initialise typescript configuration, run

npx tsc --init
Enter fullscreen mode Exit fullscreen mode

This will create a tsconfig.json file with the minimal configuration which is okay for this tutorial.

Jest Configuration

Now we will set up jest configuration for the test environment, create a file name jest.config.ts and add the below code. To learn more about jest configuration visit https://jestjs.io/docs/getting-started.

export default {
  moduleFileExtensions: ["ts", "tsx"],
  transform: {
    "^.+\\.(ts|tsx)$": "ts-jest",
  },
  testMatch: [
    "**/tests/**/*.spec.ts",
    "**/tests/**/*.test.ts",
  ],
  testEnvironment: "node",
};
Enter fullscreen mode Exit fullscreen mode

A little about the properties above.

  • moduleFileExtensions: An array of file extensions your modules use.
  • transform: This is to tell jest that we will be using a different file extension not the usual .js files, in our case we are using .ts so we passed a Regex to match our file type and a module to help handle the filetype, this is where we make use of ts-jest we installed.
  • testMatch: This property is to tell jest the directory and/or files to run for test cases.
  • testEnvironment: We are telling jest which environment our project is targeted for in this case we are targeting Node environment.

Directory

This is our proposed directory structure

├──jest.config.ts
├──package.json
├──package-lock.json
├──tsconfig.json
├──server.ts
├──src
│   └──  app.ts
└──tests
     └── app.spec.ts

It is preferred to structure your app directory in this format for testing.

Now the Codes

Create a folder name src and create a file in the src folder name app.ts with the following code.

import express, { Request, Response } from 'express';
const app = express();

app.get('/', (req: Request, res: Response): Response => {
  return res.status(200).json({message: 'Hello World!'})
});

export default app;
Enter fullscreen mode Exit fullscreen mode

Let work through the codes

  • First, we import the express module and types of Request, Response.
  • Then we initialize our application with express and assign it to a variable.
  • Then we call the get method for routing, with a callback handler for our route, this callback takes to parameter req type Request, res type Response, which returns a Response type. For typescript, it is recommended to type variables, parameters and function returns in your codes.
  • The handler returns a response of Http status code (Learn More about status codes here) status: 200 and a json of message: 'Hello World!'
  • Then we export our application for testing purposes and to modularize.

Was that a lot?

Next we create another file in the root folder and name it server.ts containing the following code.

import app from './src/app';

const server = app.listen(3000, ()=> {
  console.log(`This is running on port 3000`)
})

export default server
Enter fullscreen mode Exit fullscreen mode

This is our application entry point.

Test

Create a directory name tests in the directory create a file name app.spec.ts

import request from "supertest";
import {Express} from 'express-serve-static-core';
import app from "../src/app"

let server: Express

describe('APP should say "Hello World!"', () => {
  beforeAll(() => {
    server = app;
  });

  it('should return 200',  (done) => {
    request(server)
      .get('/')
      .expect(200)
      .end((err, res) => {
        if (err) return done(err)
        expect(res.body).toMatchObject({'message': `Hello World!`})
        done()
      })
  });
});
Enter fullscreen mode Exit fullscreen mode

Let walk through the codes

  • We import the supertest module and assign a variable name request.
  • Import the Express interface
  • We then import our app
  • Then we declare a variable server with the type of Express without assigning any value to
  • We describe our test suite for a test block, describe is a jest global function that accepts a string and a callback.
  • We passed a description of the test suite with a string APP should say "Hello World!".
  • In the callback scope we call another global function beforeAll which accepts a callback, this is to tell jest that we will want to run a block of code before the suite run its tests. In this case, we want to first assign a value to our declared variable server which will be to assign the app that we have imported so we can run our tests.
  • Then we call another global function it which is a test closure that takes two parameters a name and a callback.
  • The name of our test closure is should return 200.
  • We then pass a done callback as a parameter for the closure callback
  • We call request and pass the server variable to run our app in the test scope.
  • We call the get method and pass '/' route. In this case, we are running HTTP GET Method to our application on the route '/'.
  • We expect the HTTP GET Method should return 200 Ok status and return the message 'meesage':'Hello World!'
  • We call end method which takes a callback with two parameters of error and respond
  • We check if the test has error then we return the done method with the error from the test if otherwise which means the test runs successfully, so we call the expect global method which we check body property from the respond parameter to match our expected result.
  • Then we finally call done method to tell jest we are through with this suite.

Before we are done

And finally, we need a way to run our test, so edit the package.json file in the scripts section change test property. The scripts section should look something like this.

...
  "scripts": {
    "dev": "ts-node server.ts",
    "test": "jest --verbose"
  },
...
Enter fullscreen mode Exit fullscreen mode

Now when you run

npm run test
Enter fullscreen mode Exit fullscreen mode

You see result like the screenshot below.

Image of a successful test

Let say we change the return value in app.ts file from Hello World! to World Hello you should see a failed test like the screenshot below.

Image of a failed test

Conclusion

Writing test could mean to write more code but a few lines are worth the hours it will save. I'm a Full Stack developer using React, React Native and Nodejs with Typescript.

Feel free to put questions or suggestion in the comment or you can also reach out on Twitter iNidAName

Top comments (0)