Тестування додатків NestJS: Юніт-тести та інтеграційне тестування

NestJS — це потужний і надійний фреймворк для розробки ефективних, надійних та масштабованих серверних застосунків. Тестування є важливою частиною розробки програмного забезпечення; воно забезпечує якість коду, підвищує його підтримуваність і спрощує налагодження. NestJS надає відмінну підтримку для тестування, і в цій статті ми розглянемо, як писати юніт-тести та інтеграційні тести в додатку NestJS, використовуючи TypeScript.

Налаштування проєкту NestJS

Спочатку налаштуємо проєкт NestJS, якщо ви ще цього не зробили.

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

Перейдіть до вашої директорії проєкту:

cd project-name

Тестувальні фреймворки

NestJS використовує Jest як фреймворк для тестування за замовчуванням, який є потужною та гнучкою бібліотекою для тестування з вбудованою підтримкою TypeScript.
Jest дозволяє легко налаштовувати тестування та надає багатий набір функцій, включаючи мокінг, знімки (snapshots) та звіти про покриття.

Переконаймося, що необхідні пакети встановлені:

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

Написання юніт-тестів

Юніт-тести — це маленькі, ізольовані тести, які перевіряють поведінку окремих компонентів, таких як сервіси або контролери, без зовнішніх залежностей, як-от бази даних або інші сервіси.

Приклад: Тестування Сервісу

Розглянемо простий CatsService, який управляє додаванням та отриманням котів.

// 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;  
 }  
}

Тепер напишемо юніт-тести для 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);  
 });  

 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]);  
 });  
});

Тестування Контролера

Давайте додамо простий 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();  
 }  
}

Тепер напишемо юніт-тест для 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);  
 service = module.get(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 }]);  
 });  
});

Написання інтеграційних тестів

Інтеграційні тести перевіряють взаємодію між кількома компонентами, такими як контролери, сервіси та бази даних, щоб переконатися, що вони працюють разом як очікується.

Приклад: Тестування з базою даних

Для демонстрації інтеграційного тестування припустимо, що ми маємо сутність Cat з використанням 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;  
}

Нам знадобиться репозиторій для підключення нашого сервісу до бази даних.

// 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();  
 }  
}

Далі напишемо інтеграційні тести.
Ми повинні налаштувати базу даних в пам'яті для тестування, наприклад, 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);  
 });  

 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 }),  
 ]),  
 );  
 });  
});

Висновок

Тестування додатків на NestJS є важливою частиною для забезпечення якості та підтримуваності. Написання юніт-тестів для ізольованих компонентів та інтеграційних тестів для перевірки взаємодії між компонентами дозволяє створювати надійні та стабільні додатки. Завдяки Jest і вбудованим тестовим утилітам NestJS, написання та виконання тестів стає простим та ефективним.

Тепер ви готові ефективно почати тестувати свої додатки на NestJS. Успіхів у програмуванні!

Перекладено з: Testing NestJS Applications: Unit and Integration Testing

Leave a Reply

Your email address will not be published. Required fields are marked *