Testing NestJS Applications: Unit and Integration Testing

@rnab
4 min readJan 2, 2025

--

NestJS is a powerful and robust framework for building efficient, reliable, and scalable server-side applications. Testing is a vital part of software development; it ensures code quality, increases maintainability, and simplifies debugging. NestJS provides excellent support for testing, and this article will guide you through writing unit and integration tests in a NestJS application using TypeScript.

Setting Up a NestJS Project

First, let’s set up a NestJS project if you haven’t already.

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

Navigate into your project directory:

cd project-name

Testing Frameworks

NestJS uses Jest as the default testing framework, which is a powerful and flexible testing library with built-in TypeScript support. Jest enables easy setup and provides a rich set of features, including mocking, snapshots, and coverage reports.

Let’s ensure the necessary packages are installed:

npm install sqlite3 --savenpm install --save-dev jest @types/jest ts-jest @nestjs/testing

Writing Unit Tests

Unit tests are small, isolated tests that validate the behavior of individual components, such as services or controllers, without external dependencies like databases or other services.

Example: Testing a Service

Consider a simple CatsService which manages adding and retrieving cats.

// src/cats/cats.service.ts
import { Injectable } from '@nestjs/common';

interface Cat {
id: number;
name: string;
age: number;
}

@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];

create(cat: Cat) {
this.cats.push(cat);
}

findAll(): Cat[] {
return this.cats;
}
}

Now, let’s write unit tests for CatsService.

// src/cats/cats.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { CatsService } from './cats.service';

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

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

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

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

it('should create a cat', () => {
const cat = { id: 1, name: 'Tom', age: 3 };
service.create(cat);
expect(service.findAll()).toEqual([cat]);
});

it('should return all cats', () => {
const cat1 = { id: 1, name: 'Tom', age: 3 };
const cat2 = { id: 2, name: 'Jerry', age: 2 };
service.create(cat1);
service.create(cat2);
expect(service.findAll()).toEqual([cat1, cat2]);
});
});

Testing a Controller

Let’s add a simple CatsController.

// src/cats/cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatsService } from './cats.service';

interface Cat {
id: number;
name: string;
age: number;
}

@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}

@Post()
create(@Body() cat: Cat) {
this.catsService.create(cat);
}

@Get()
findAll(): Cat[] {
return this.catsService.findAll();
}
}

Now, let’s write a unit test for CatsController.

// src/cats/cats.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

describe('CatsController', () => {
let controller: CatsController;
let service: CatsService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CatsController],
providers: [
{
provide: CatsService,
useValue: {
create: jest.fn(),
findAll: jest.fn().mockReturnValue([{ id: 1, name: 'Tom', age: 3 }]),
},
},
],
}).compile();

controller = module.get<CatsController>(CatsController);
service = module.get<CatsService>(CatsService);
});

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

it('should create a cat', () => {
const cat = { id: 1, name: 'Tom', age: 3 };
controller.create(cat);
expect(service.create).toHaveBeenCalledWith(cat);
});

it('should return all cats', () => {
expect(controller.findAll()).toEqual([{ id: 1, name: 'Tom', age: 3 }]);
});
});

Writing Integration Tests

Integration tests validate the interaction between multiple components, such as controllers, services, and databases, to ensure they work together as expected.

Example: Testing with a Database

To demonstrate integration testing, let’s assume we have a Cat entity with TypeORM.

// src/cats/cat.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Cat {
@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;

@Column()
age: number;
}

We’ll need a repository to connect our service to a database.

// src/cats/cats.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Cat } from './cat.entity';

@Injectable()
export class CatsService {
constructor(
@InjectRepository(Cat)
private readonly catsRepository: Repository<Cat>,
) {}

create(cat: Partial<Cat>): Promise<Cat> {
const newCat = this.catsRepository.create(cat);
return this.catsRepository.save(newCat);
}

findAll(): Promise<Cat[]> {
return this.catsRepository.find();
}
}

Next, let’s write integration tests. We need to set up an in-memory database for testing, such as SQLite.

// src/cats/cats.service.integration.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CatsService } from './cats.service';
import { Cat } from './cat.entity';

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

beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
type: 'sqlite',
database: ':memory:',
entities: [Cat],
synchronize: true,
}),
TypeOrmModule.forFeature([Cat]),
],
providers: [CatsService],
}).compile();

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

afterEach(async () => {
await service.create({ name: 'Tom', age: 3 });
await service.create({ name: 'Jerry', age: 2 });
});

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

it('should create a cat', async () => {
const cat = { name: 'Tom', age: 3 };
const createdCat = await service.create(cat);
expect(createdCat).toEqual(expect.objectContaining(cat));
});

it('should return all cats', async () => {
const cats = await service.findAll();
expect(cats.length).toBe(2);
expect(cats).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: 'Tom', age: 3 }),
expect.objectContaining({ name: 'Jerry', age: 2 }),
]),
);
});
});

Conclusion

Testing NestJS applications is essential to ensure quality and maintainability. By writing unit tests for isolated components and integration tests for validating interactions between components, you can create robust and dependable applications. With Jest and NestJS’s built-in testing utilities, writing and running tests becomes straightforward and efficient.

Now you are equipped to start testing your NestJS applications effectively. Happy coding!

--

--

@rnab
@rnab

Written by @rnab

Typescript, Devops, Kubernetes, AWS, AI/ML, Algo Trading

Responses (1)