Open In App

How To Write Integration Tests in NestJS?

Last Updated : 23 Sep, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

Integration testing is an important part of software development, ensuring that various parts of your application work together as expected. In NestJS, integration testing allows you to test the interaction between different modules, services, and controllers.

In this article, we will guide you through the process of writing integration tests in NestJS, covering essential concepts, tools, and examples to help you implement robust tests for your application.

What are Integration Tests?

Integration testing is the phase in software testing where individual components or systems are combined and tested as a group. Unlike unit testing, which focuses on testing isolated components in isolation, integration testing ensures that these components work together as expected.

In NestJS, integration tests typically involve testing how modules, controllers, services, and external resources (like databases and APIs) interact. It ensures that your application performs correctly under real-world conditions.

Steps To Write Integration Tests in NestJS

Step 1: Set Up Your Testing Environment

npm i -g @nestjs/cli
nest new project-name

Step 2: Create the Patient and Appointment Modules

nest g module patient
nest g module appointment

Step 3: Create the Services and Controllers

nest g service patient
nest g controller patient
nest g service appointment
nest g controller appointment

Folder Structure

Folder-Structure
Folder Structure

Dependencies

"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
},

Step 4: Write Integration Tests

Create a test file for your appointment module

appointment.model.ts
appointment.module.ts
appointment.service.spec.ts
appointment.service.ts
JavaScript
//src/appointment/appointment.model.ts

export interface Appointment {
    patientId: number;
    startTime: Date;
    endTime: Date;
    confirmed: boolean;
}
JavaScript
//src/appointment/appointment.module.ts

import { Module } from '@nestjs/common';
import { PatientModule } from '../patient/patient.module';
import { AppointmentService } from './appointment.service';

@Module({
    imports: [PatientModule],
    providers: [AppointmentService],
})
export class AppointmentModule { }
JavaScript
//src/appointment/appointment.service.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { PatientModule } from '../patient/patient.module';
import { PatientService } from '../patient/patient.service';
import { AppointmentService } from './appointment.service';

describe('AppointmentService', () => {
    let service: AppointmentService;
    let patientService: PatientService;

    beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
            imports: [PatientModule],
            providers: [AppointmentService],
        }).compile();

        service = module.get<AppointmentService>(AppointmentService);
        patientService = module.get(PatientService);
    });

    it('should be defined', () => {
        expect(service).toBeDefined();
    });

    it('should schedule an unconfirmed appointment for a user on success', async () => {
        const startTime = new Date('2022-01-01T14:00:00Z');
        const endTime = new Date('2022-01-01T15:00:00Z');
        const { id: patientId } = await patientService.register({
            name: 'John Doe',
        });

        const newAppointment = await service.scheduleAppointment({
            patientId,
            startTime,
            endTime,
        });

        expect(newAppointment).toEqual({
            patientId,
            startTime,
            endTime,
            confirmed: false,
        });
    });

    it('should throw an error when end time is before start time', async () => {
        const startTime = new Date('2022-01-01T14:00:00Z');
        const endTime = new Date('2022-01-01T13:00:00Z');

        await expect(
            service.scheduleAppointment({
                patientId: 1,
                startTime,
                endTime,
            }),
        ).rejects.toThrowError("appointment's endTime should be after startTime");
    });

    it('should throw an error when end time is equal to start time', async () => {
        const startTime = new Date('2022-01-01T14:00:00Z');
        const endTime = startTime;

        await expect(
            service.scheduleAppointment({
                patientId: 1,
                startTime,
                endTime,
            }),
        ).rejects.toThrowError("appointment's endTime should be after startTime");
    });

    it('should throw an error when end time is in the next day', async () => {
        const startTime = new Date('2022-01-01T14:00:00Z');
        const endTime = new Date('2022-01-02T00:00:00Z');

        await expect(
            service.scheduleAppointment({
                patientId: 1,
                startTime,
                endTime,
            }),
        ).rejects.toThrowError(
            "appointment's endTime should be in the same day as start time's",
        );
    });

    it('should throw an error when end time is in same day and hour of next month', async () => {
        const startTime = new Date('2022-01-01T14:00:00Z');
        const endTime = new Date('2022-02-01T14:00:00Z');

        await expect(
            service.scheduleAppointment({
                patientId: 1,
                startTime,
                endTime,
            }),
        ).rejects.toThrowError(
            "appointment's endTime should be in the same day as start time's",
        );
    });

    it('should throw an error when end time is in same day, hour and month of the next year', async () => {
        const startTime = new Date('2022-01-01T14:00:00Z');
        const endTime = new Date('2023-01-01T14:00:00Z');

        await expect(
            service.scheduleAppointment({
                patientId: 1,
                startTime,
                endTime,
            }),
        ).rejects.toThrowError(
            "appointment's endTime should be in the same day as start time's",
        );
    });

    it('should throw an error when the patient does not exist', async () => {
        const startTime = new Date('2022-01-01T14:00:00Z');
        const endTime = new Date('2022-01-01T15:00:00Z');

        await expect(
            service.scheduleAppointment({
                patientId: 1,
                startTime,
                endTime,
            }),
        ).rejects.toThrowError('Patient does not exist');
    });
});
JavaScript
//src/appointment/appointment.service.ts

import { Injectable } from '@nestjs/common';
import { PatientService } from '../patient/patient.service';
import { Appointment } from './appointment.model';

export interface AppointmentInput {
    patientId: number;
    startTime: Date;
    endTime: Date;
}

@Injectable()
export class AppointmentService {
    constructor(private readonly patientService: PatientService) { }

    public async scheduleAppointment(
        appointmentData: AppointmentInput,
    ): Promise<Appointment> {
        if (appointmentData.endTime <= appointmentData.startTime) {
            throw new Error("appointment's endTime should be after startTime");
        }

        if (this.endTimeIsInTheNextDay(appointmentData)) {
            throw new Error(
                "appointment's endTime should be in the same day as start time's",
            );
        }

        const patientExists = await this.patientService.doesPatientExist(
            appointmentData.patientId,
        );

        if (!patientExists) {
            throw new Error('Patient does not exist');
        }

        return {
            ...appointmentData,
            confirmed: false,
        };
    }

    private endTimeIsInTheNextDay(appointmentData: AppointmentInput): boolean {
        const differentDays =
            appointmentData.endTime.getUTCDate() !==
            appointmentData.startTime.getUTCDate();

        const differentMonths =
            appointmentData.endTime.getUTCMonth() !==
            appointmentData.startTime.getUTCMonth();

        const differentYears =
            appointmentData.endTime.getUTCFullYear() !==
            appointmentData.startTime.getUTCFullYear();

        return differentDays || differentMonths || differentYears;
    }
}


Create a test file for your patient module

patient.model.ts
patient.modules.ts
patient.service.spec.ts
patient.service.ts
JavaScript
//src/patient/patient.model.ts

export interface Patient {
    id: number;
    name: string;
}
JavaScript
//src/patient/patient.module.ts

import { Module } from '@nestjs/common';
import { PatientService } from './patient.service';

@Module({
    providers: [PatientService],
    exports: [PatientService],
})
export class PatientModule { }
JavaScript
//src/patient/patient.service.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { PatientService } from './patient.service';

describe('PatientService', () => {
    let service: PatientService;

    beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
            providers: [PatientService],
        }).compile();

        service = module.get<PatientService>(PatientService);
    });

    it('should be defined', () => {
        expect(service).toBeDefined();
    });

    describe('register', () => {
        it('should return a new patient with given name', async () => {
            const newPatient = await service.register({ name: 'John Doe' });

            expect(newPatient).toEqual({
                id: expect.any(Number),
                name: 'John Doe',
            });
        });

        it('should return different patients when called twice', async () => {
            const firstPatient = await service.register({ name: 'John Doe' });
            const secondPatient = await service.register({ name: 'John Doe' });

            expect(firstPatient).not.toEqual(secondPatient);
        });
    });

    describe('doesPatientExist', () => {
        it('should return false when no patient was registered', async () => {
            const patientId = 1;
            const exists = await service.doesPatientExist(patientId);

            expect(exists).toBe(false);
        });

        it('should return true when patient was registered', async () => {
            const { id: patientId } = await service.register({ name: 'John Doe' });
            const exists = await service.doesPatientExist(patientId);

            expect(exists).toBe(true);
        });
    });
});
JavaScript
//src/patient/patient.service.ts

import { Injectable } from '@nestjs/common';
import { Patient } from './patient.model';

export interface PatientInput {
    name: string;
}

@Injectable()
export class PatientService {
    private readonly patients: Patient[] = [];
    private nextId = 1;

    public async register(patientInput: PatientInput): Promise<Patient> {
        const newPatient = {
            id: this.nextId++,
            name: patientInput.name,
        };

        this.patients.push(newPatient);

        return newPatient;
    }

    public async doesPatientExist(patientId: number): Promise<boolean> {
        return this.patients.some((patient) => patient.id === patientId);
    }
}


Create a app files in src folder

app.controller.spec.ts
app.controller.ts
app.module.ts
app.service.ts
main.ts
JavaScript
//src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    await app.listen(3000);
}
bootstrap();
JavaScript
//src/app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PatientModule } from './patient/patient.module';
import { AppointmentModule } from './appointment/appointment.module';

@Module({
    imports: [PatientModule, AppointmentModule],
    controllers: [AppController],
    providers: [AppService],
})
export class AppModule { }
JavaScript
//src/app.service.ts

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
    getHello(): string {
        return 'Hello World!';
    }
}
JavaScript
//src/app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
    constructor(private readonly appService: AppService) { }

    @Get()
    getHello(): string {
        return this.appService.getHello();
    }
}
JavaScript
//src/app.controller.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';

describe('AppController', () => {
    let appController: AppController;

    beforeEach(async () => {
        const app: TestingModule = await Test.createTestingModule({
            controllers: [AppController],
            providers: [AppService],
        }).compile();

        appController = app.get<AppController>(AppController);
    });

    describe('root', () => {
        it('should return "Hello World!"', () => {
            expect(appController.getHello()).toBe('Hello World!');
        });
    });
});


Create a new Test folder with this file inside

app.e2e-spec.ts
JavaScript
//test/app.e2e-spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { PatientModule } from '../src/patient/patient.module';
import { PatientService } from '../src/patient/patient.service';

describe('PatientModule (e2e)', () => {
    let app: INestApplication;
    let patientService = {
        findAll: () => [{ id: 1, name: 'John Doe', age: 30 }],
        create: (dto: any) => ({ id: 2, ...dto }),
    };

    // Setup before tests
    beforeAll(async () => {
        const moduleFixture: TestingModule = await Test.createTestingModule({
            imports: [PatientModule],
        })
            .overrideProvider(PatientService)
            .useValue(patientService)
            .compile();

        app = moduleFixture.createNestApplication();
        await app.init();
    });

    // Integration test for GET /patients endpoint
    it('/patients (GET)', () => {
        return request(app.getHttpServer())
            .get('/patients')
            .expect(200)
            .expect([{ id: 1, name: 'John Doe', age: 30 }]);
    });

    // Integration test for POST /patients endpoint
    it('/patients (POST)', () => {
        return request(app.getHttpServer())
            .post('/patients')
            .send({ name: 'Jane Doe', age: 28 })
            .expect(201)
            .expect({ id: 2, name: 'Jane Doe', age: 28 });
    });

    // Cleanup after tests
    afterAll(async () => {
        await app.close();
    });
});


Step 5: Start and Run the Test

npm start test
nest-start
How To Write Integration Tests in NestJS

Run the test using the following command

npm test --
nest-test
Run the test

Next Article

Similar Reads