Merhaba,

 

Bu yazıda NestJS ile Unit Test kullanımından bahsedeceğim.

 

İlk olarak bir NestJS projesi oluşturuyoruz:

nest new nest-unit-test-sample

 

Projede ürün ekleme, listeleme ve silme şeklinde CRUD işlemleri yapacağız. Veritabanı olarak MySQL kullanacağız. API hazır olduktan sonra da unit testleri yazacağız. MySQL için gerekli paketleri kuralım:

npm i @nestjs/typeorm typeorm mysql

 

Local ortamımızda nest_unit_test_sample_db adında bir veritabanı oluşturup içerisine products tablosu ekliyoruz. Bu tabloya da id, name, description ve price sütunları ekliyoruz.

 

app.module.ts dosyasını açarak TypeOrm modülünü dahil ediyoruz ve veritabanı bilgilerimizi yazıyoruz:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';

@Module({
  imports: [TypeOrmModule.forRoot({
  	"type": "mysql",
  	"host": "localhost",
  	"port": 3306,
  	"username": "root",
  	"password": "12345678",
  	"database": "nest_unit_test_sample_db",
  	"synchronize": false,
  	"logging": true,
  	"entities": ["dist/**/*.entity.js"]
  }), ProductsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

 

products modülü oluşturuyoruz:

nest g module products

 

Veritabanındaki products tablosuna karşılık gelecek şekilde src/products altında products.entity.ts adında bir entity oluşturuyoruz:

import { PrimaryGeneratedColumn, Column, Entity } from 'typeorm';

@Entity('products')
export class ProductsEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  description: string;

  @Column()
  price: string;
}

 

src/products altına dto klasörü oluşturuyoruz. Onun altına da create-product.dto.ts dosyası oluşturuyoruz:

export class CreateProductDTO {
  id: number;
  name: string;
  description: string;
  price: string;
}

 

products servisi oluşturuyoruz:

nest g service products

 

products.service.ts:

import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ProductsEntity } from './products.entity';
import { CreateProductDTO } from './dto/create-product.dto';

@Injectable()
export class ProductsService {
  constructor(
    @InjectRepository(ProductsEntity)
    private productsRepository: Repository<ProductsEntity>,
  ) {}

  public async createProduct(
    createProductDto: CreateProductDTO,
  ): Promise<ProductsEntity> {
    return await this.productsRepository.save(createProductDto);
  }

  public async getProducts(): Promise<ProductsEntity[]> {
    return await this.productsRepository.find();
  }

  public async deleteProduct(productId: number): Promise<void> {
    await this.productsRepository.delete(productId);
  }
}

 

products controller dosyasını oluşturuyoruz:

nest g controller products --no-spec

 

products.controller.ts:

import {
  Controller,
  Post,
  Body,
  Get,
  Param,
  Delete,
} from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDTO } from './dto/create-product.dto';
import { ProductsEntity } from './products.entity';

@Controller('products')
export class ProductsController {
  constructor(private productsService: ProductsService) {}

  @Post('create')
  public async createProduct(
    @Body() createProductDto: CreateProductDTO,
  ): Promise<ProductsEntity> {
    const product = await this.productsService.createProduct(createProductDto);
    return product;
  }

  @Get('all')
  public async getProducts(): Promise<ProductsEntity[]> {
    const products = await this.productsService.getProducts();
    return products;
  }

  @Delete('/delete/:productId')
  public async deleteProduct(@Param('productId') productId: number) {
    const deletedProduct = await this.productsService.deleteProduct(productId);
    return deletedProduct;
  }
}

 

products.module.ts dosyasını açarak gerekli düzenlemeleri yapıyoruz:

import { Module } from '@nestjs/common';
import { ProductsService } from './products.service';
import { ProductsController } from './products.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ProductsEntity } from './products.entity';

@Module({
  imports: [TypeOrmModule.forFeature([ProductsEntity])],
  providers: [ProductsService],
  controllers: [ProductsController]
})
export class ProductsModule {}

 

API hazır. Projeyi npm run start komutu ile ayağa kaldırarak Postman üzerinden testleri yapabilirsiniz. Şimdi işin unit test kısmına gelelim.

 

products.service.spec.ts dosyasını açarak şu şekilde bir kodlama yapıyoruz:

import { Test, TestingModule } from '@nestjs/testing';
import { ProductsService } from './products.service';
import { ProductsEntity } from './products.entity';
import { getRepositoryToken } from '@nestjs/typeorm';

describe('ProductService', () => {
  let productService;
  let productRepository;
  const mockProductRepository = () => ({
    save: jest.fn(),
    find: jest.fn(),
    delete: jest.fn(),
  });

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ProductsService,
        {
          provide: getRepositoryToken(ProductsEntity),
          useFactory: mockProductRepository,
        },
      ],
    }).compile();
    productService = await module.get<ProductsService>(ProductsService);
    productRepository = await module.get(getRepositoryToken(ProductsEntity));
  });

  describe('createProduct', () => {
    it('should save a product in the database', async () => {
      productRepository.save.mockResolvedValue('someProduct');
      expect(productRepository.save).not.toHaveBeenCalled();
      const createProductDto = {
        name: 'sample name',
        description: 'sample description',
        price: 'sample price',
      };
      const result = await productService.createProduct(createProductDto);
      expect(productRepository.save).toHaveBeenCalledWith(
        createProductDto,
      );
      expect(result).toEqual('someProduct');
    });
  });

  describe('getProducts', () => {
    it('should get all products', async () => {
      productRepository.find.mockResolvedValue('someProducts');
      expect(productRepository.find).not.toHaveBeenCalled();
      const result = await productService.getProducts();
      expect(productRepository.find).toHaveBeenCalled();
      expect(result).toEqual('someProducts');
    });
  });

  describe('deleteProduct', () => {
    it('should delete product', async () => {
      productRepository.delete.mockResolvedValue(1);
      expect(productRepository.delete).not.toHaveBeenCalled();
      await productService.deleteProduct(1);
      expect(productRepository.delete).toHaveBeenCalledWith(1);
    });
  });
});

Burada yaptığımız işlemler;

  • beforeEach kısmında her test öncesi çalıştırılacak kodları yazdık. Burada servis ve repository için mock (sahte) tanımlamaları yaptık.
  • createProduct şeklinde bir test yazdık. Burada örnek bir product verisi tanımlayıp servis üzerindeki ürün oluşturma fonksiyonumuzu çağırdık. Servisten gelen değerle elimizdeki örnek veriyi karşılaştırdık.
  • getProducts şeklinde bir test yazdık.  Burada servis üzerindeki ürünleri listeleme fonksiyonumuzu çağırdık. Repository üzerindeki find fonksiyonunu da çağırdık ve servisten gelen değerle karşılaştırdık.
  • deleteProduct şeklinde bir test yazdık. Burada servis üzerindeki ürün silme fonksiyonumuzu çağırdık. Repository üzerindeki delete fonksiyonunu da çağırdık ve servisten gelen değerle karşılaştırdık.

 

Testlerimizi çalıştırıyoruz:

npm run test

 

Konsoldaki çıktımız şu şekilde oluyor:

 

Projenin kaynak kodlarına buradan ulaşabilirsiniz.

 

Umarım yararlı olmuştur.

 

İyi çalışmalar.