Testing with Jest in TypeScript and Node.js for Beginners

image

This article will help you to set up a Jest in your TypeScript and **Node.js **application.

Please check the article Skeleton for Node.js Apps written in TypeScript (with Setup Instructions for ESLint, Prettier, and Husky)

Prerequisites

Basic Theory (Difference between npx and npm?)

  • NPM — Manages packages but doesn’t make life easy executing any.

  • NPX — A tool for executing Node packages.

Required npm modules

  • jest

  • @types/jest

  • ts-jest

  • supertest

jest

Jest is a delightful JavaScript Testing Framework with a focus on simplicity.

npm install --save-dev jest

@types/jest

This package contains type definitions for Jest (https://jestjs.io/).

npm install --save-dev @types/jest

ts-jest

This is the TypeScript preprocessor for jest (ts-jest) which allows jest to transpile TypeScript on the fly and have source-map support built-in.

A Jest transformer with source map support that lets you use Jest to test projects written in TypeScript.

npm install --save-dev ts-jest

supertest

SuperTest is an HTTP assertions library that allows you to test your Node.js HTTP servers. It is built on top of the SuperAgent library, which is an HTTP client for Node.js.

npm install --save-dev supertest

Configure Jest

Step 1. Basic Configuration

Add the following jest.config.js file to the root of your project:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

Step 2. Setup Global Environment Variable using dotenv with Jest Testing

Add the following code in jest.config.js file to access the environment variable in spec files.

setupFiles: ['dotenv/config'],

After adding the above file jest configuration will look like

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  setupFiles: ['dotenv/config'],
};

Now you can access the .env variables in the test cases file.

Example: process.env.BASE_URL

Step 3. Add the action script in package.json

Add below action script in the file package.json to run the test cases.

"test": "jest --coverage",
"test:watch": "jest --watch"

Folder Structure

Please find below the folder structure and description of each folder tests, tests/helpers, tests/unit-tests and tests/integration-tests respectively.

folder structurefolder structure

App Running Instance Helper

If we want to test any endpoint then we need to run the app in another terminal and using supertest the module we can consume that API.

To avoid this we can create one integration helper which will run the app on the fly to test particular components.

Create tests/helpers/Integration-helpers.ts file and add the below code in it. You can use this file to create another helper which may require you in test cases.

import * as express from 'express';
import App from '../../src/App';
import Environment from '../../src/environments/environment';
import logger from '../../src/lib/logger';

export default class IntegrationHelpers {

    public static appInstance: express.Application;

    public static async getApp(): Promise<express.Application> {
        if (this.appInstance) {
            return this.appInstance;
        }
        const env: Environment = new Environment(process.env.NODE_ENV);
        const app: App = new App(env);
        await app.init();
        this.appInstance = app.express;

        return this.appInstance;
    }

    public clearDatabase(): void {
        logger.info('clear the database');
    }

}

Unit test

Test logic in classes by programmers to show code level correctness. They should be fast and not dependent on other parts of the system that you don’t intend to test.

Here we can test the particular class or service or controller which we have used in our application.

For example, we have created few test cases for environment-class which we used in our application to set up/configure the environment variable.

import 'jest';
import Environment from '../../../src/environments/environment';
import { Environments } from '../../../src/environments/environment.constant';

describe('Environment', () => {
    let instance: Environment;

    beforeEach(() => {
        instance = new Environment('local');
    });

    it('should get the current environment', async () => {
        expect(instance).toBeInstanceOf(Environment);
        const environment = instance.currentEnvironment();
        expect(environment).toBeDefined();
        expect(environment).toBe(Environments.LOCAL);
    });

    it('should check if environement is production or not', async () => {
        const result = instance.isProduction();
        expect(result).toBe(false);
    });

    it('should check if environement is production or not', async () => {
        const result = instance.isProduction();
        expect(result).toBe(false);
    });

    it('should set the current environment', async () => {
        instance.setEnvironment('local');
        const environment = instance.currentEnvironment();
        expect(environment).toBeDefined();
        expect(environment).toBe(Environments.LOCAL);
    });
});

Integration Test Cases

Test communication paths between different parts of the module are done by the test department or by developers to show that all modules work correctly together.

Here we are going to test the endpoints or API which we created to consume from the client-side.

Please find below the source code for status component integration test cases.

import 'jest';
import * as express from 'express';
import * as request from 'supertest';
import {
    StatusCodes,
} from 'http-status-codes';
import IntegrationHelpers from '../helpers/Integration-helpers';

describe('status integration tests', () => {
    let app: express.Application;

    beforeAll(async() => {
        app = await IntegrationHelpers.getApp();
    });


    it('can get server time', async () => {
        await request(app)
            .get('/api/status/time')
            .set('Accept', 'application/json')
            .expect((res: request.Response) => {
                // eslint-disable-next-line no-console
                console.log(res.text);
            })
            .expect(StatusCodes.OK);
    });

    it('can get server system info', async () => {
        await request(app)
            .get('/api/status/system')
            .set('Accept', 'application/json')
            .expect(StatusCodes.OK);
    });

    it('can get server system usage', async () => {
        await request(app)
            .get('/api/status/usage')
            .set('Accept', 'application/json')
            .expect(StatusCodes.OK);
    });

    it('can get server system process info', async () => {
        await request(app)
            .get('/api/status/process')
            .set('Accept', 'application/json')
            .expect(StatusCodes.OK);
    });

    it('should get the error', async () => {
        await request(app)
            .get('/api/status/error')
            .set('Accept', 'application/json')
            .expect(StatusCodes.BAD_REQUEST);
    });

});

Run The Test Cases

To run the test cases please hit the command npm run test on the terminal.

Terminal

You can also check the detailed report in coverage folder.

Output Folder

If you open the index.html in the browser then you will get the result for each file code coverage.

coverage

References

Skeleton for Node.js Apps Written in TypeScript

GitHub - santoshshinde2012/node-boilerplate: Node Typescript Boilerplate for Microservices

Santosh Shinde on LinkedIn

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics