From cb9dfd8717ea66268d2398048fcd266a8916e937 Mon Sep 17 00:00:00 2001 From: Didara Date: Mon, 3 Nov 2025 18:01:06 +0100 Subject: [PATCH] sample(sample/10): add unit and e2e tests for 10-fastify fix(sample/10): fix id generation and controller return values - fix bug in CatsService (id generation) - fix multiple bugs in CatsController (no return, @Res() usage) feat(sample/10): add full test coverage - add complete unit test suite for CatsService - add complete e2e test suite for CatsController - add Jest config and dependencies to the sample Fixes #1539 --- sample/10-fastify/e2e/cats.e2e-spec.ts | 85 +++++++++++++++++++ sample/10-fastify/e2e/jest-e2e.json | 9 ++ sample/10-fastify/jest.config.js | 7 ++ sample/10-fastify/package.json | 5 +- sample/10-fastify/src/cats/cats.controller.ts | 38 +++++---- .../10-fastify/src/cats/cats.service.spec.ts | 57 +++++++++++++ sample/10-fastify/src/cats/cats.service.ts | 18 +++- .../src/cats/interfaces/cat.interface.ts | 1 + 8 files changed, 200 insertions(+), 20 deletions(-) create mode 100644 sample/10-fastify/e2e/cats.e2e-spec.ts create mode 100644 sample/10-fastify/e2e/jest-e2e.json create mode 100644 sample/10-fastify/jest.config.js create mode 100644 sample/10-fastify/src/cats/cats.service.spec.ts diff --git a/sample/10-fastify/e2e/cats.e2e-spec.ts b/sample/10-fastify/e2e/cats.e2e-spec.ts new file mode 100644 index 00000000000..66e6d6cc615 --- /dev/null +++ b/sample/10-fastify/e2e/cats.e2e-spec.ts @@ -0,0 +1,85 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { CatsModule } from '../src/cats/cats.module'; +import { + FastifyAdapter, + NestFastifyApplication, +} from '@nestjs/platform-fastify'; +import { CreateCatDto } from '../src/cats/dto/create-cat.dto'; +import { RolesGuard } from '../src/common/guards/roles.guard'; + +describe('CatsController (e2e)', () => { + let app: INestApplication; + + const mockRolesGuard = { canActivate: () => true }; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [CatsModule], + }) + .overrideGuard(RolesGuard) + .useValue(mockRolesGuard) + .compile(); + + app = moduleFixture.createNestApplication( + new FastifyAdapter(), + ); + + await app.init(); + await app.getHttpAdapter().getInstance().ready(); + }); + + afterAll(async () => { + await app.close(); + }); + + it('/cats (GET) - initial state', () => { + return request(app.getHttpServer()) + .get('/cats') + .expect(200) + .expect([]); + }); + + it('/cats (POST) - create a cat', () => { + const newCatDto: CreateCatDto = { name: 'Milo', age: 2, breed: 'Tabby' }; + const expectedCat = { + id: 1, + ...newCatDto, + }; + + return request(app.getHttpServer()) + .post('/cats') + .send(newCatDto) + .expect(201) + .expect(expectedCat); + }); + + it('/cats (GET) - after create', () => { + const expectedCat = { + id: 1, + name: 'Milo', + age: 2, + breed: 'Tabby', + }; + + return request(app.getHttpServer()) + .get('/cats') + .expect(200) + .expect([expectedCat]); + }); + + it('/cats/:id (GET) - find one', () => { + const expectedCat = { + id: 1, + name: 'Milo', + age: 2, + breed: 'Tabby', + }; + + return request(app.getHttpServer()) + .get('/cats/1') + .expect(200) + .expect(expectedCat); + }); +}); \ No newline at end of file diff --git a/sample/10-fastify/e2e/jest-e2e.json b/sample/10-fastify/e2e/jest-e2e.json new file mode 100644 index 00000000000..ac432d937e1 --- /dev/null +++ b/sample/10-fastify/e2e/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} \ No newline at end of file diff --git a/sample/10-fastify/jest.config.js b/sample/10-fastify/jest.config.js new file mode 100644 index 00000000000..e4ab2b3118a --- /dev/null +++ b/sample/10-fastify/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + rootDir: 'src', + testRegex: '.*\\.spec\\.ts$', + moduleFileExtensions: ['js', 'json', 'ts'], +}; \ No newline at end of file diff --git a/sample/10-fastify/package.json b/sample/10-fastify/package.json index c3da2b34fb8..3f786969960 100644 --- a/sample/10-fastify/package.json +++ b/sample/10-fastify/package.json @@ -16,7 +16,7 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "echo 'No e2e tests implemented yet.'" + "test:e2e": "jest --config ./e2e/jest-e2e.json" }, "dependencies": { "@nestjs/common": "11.1.8", @@ -35,6 +35,7 @@ "@nestjs/schematics": "11.0.9", "@nestjs/testing": "11.1.8", "@types/node": "24.10.0", + "@types/jest": "^30.0.0", "@types/supertest": "6.0.3", "eslint": "9.38.0", "eslint-plugin-prettier": "5.5.4", @@ -42,7 +43,7 @@ "jest": "30.2.0", "prettier": "3.6.2", "supertest": "7.1.4", - "ts-jest": "29.4.5", + "ts-jest": "^29.4.5", "ts-loader": "9.5.4", "ts-node": "10.9.2", "tsconfig-paths": "4.2.0", diff --git a/sample/10-fastify/src/cats/cats.controller.ts b/sample/10-fastify/src/cats/cats.controller.ts index 2008e3de796..05bdb4f6e0c 100644 --- a/sample/10-fastify/src/cats/cats.controller.ts +++ b/sample/10-fastify/src/cats/cats.controller.ts @@ -1,32 +1,40 @@ -import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; -import { Roles } from '../common/decorators/roles.decorator'; -import { RolesGuard } from '../common/guards/roles.guard'; -import { ParseIntPipe } from '../common/pipes/parse-int.pipe'; -import { CatsService } from './cats.service'; +import { + Controller, + Get, + Post, + Body, + Param, + UseGuards, + NotFoundException, +} from '@nestjs/common'; import { CreateCatDto } from './dto/create-cat.dto'; +import { CatsService } from './cats.service'; +import { RolesGuard } from '../common/guards/roles.guard'; +import { Roles } from '../common/decorators/roles.decorator'; import { Cat } from './interfaces/cat.interface'; -@UseGuards(RolesGuard) @Controller('cats') +@UseGuards(RolesGuard) export class CatsController { constructor(private readonly catsService: CatsService) {} @Post() @Roles('admin') - async create(@Body() createCatDto: CreateCatDto) { - this.catsService.create(createCatDto); + create(@Body() createCatDto: CreateCatDto): Cat { + return this.catsService.create(createCatDto); } @Get() - async findAll(): Promise { + findAll(): Cat[] { return this.catsService.findAll(); } @Get(':id') - findOne( - @Param('id', new ParseIntPipe()) - id: number, - ) { - // get by ID logic + findOne(@Param('id') id: string): Cat { + const cat = this.catsService.findOne(+id); + if (!cat) { + throw new NotFoundException(`Cat with ID ${id} not found`); + } + return cat; } -} +} \ No newline at end of file diff --git a/sample/10-fastify/src/cats/cats.service.spec.ts b/sample/10-fastify/src/cats/cats.service.spec.ts new file mode 100644 index 00000000000..177ba5dca0e --- /dev/null +++ b/sample/10-fastify/src/cats/cats.service.spec.ts @@ -0,0 +1,57 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { CatsService } from './cats.service'; +import { Cat } from './interfaces/cat.interface'; +import { CreateCatDto } from './dto/create-cat.dto'; + +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(); + }); + + describe('create and findAll', () => { + it('should create a new cat, assign an ID, and return all cats', () => { + const catDto: CreateCatDto = { name: 'Milo', age: 2, breed: 'Tabby' }; + + const createdCat = service.create(catDto); + + expect(createdCat.id).toBe(1); + expect(createdCat.name).toBe('Milo'); + + const allCats = service.findAll(); + + expect(allCats).toHaveLength(1); + expect(allCats).toEqual([createdCat]); + + const catDto2: CreateCatDto = { name: 'Luna', age: 1, breed: 'Siamese' }; + const createdCat2 = service.create(catDto2); + expect(createdCat2.id).toBe(2); + }); + }); + + describe('findOne', () => { + it('should return a single cat by its id', () => { + const catDto: CreateCatDto = { name: 'Luna', age: 1, breed: 'Siamese' }; + const createdCat = service.create(catDto); + + const foundCat = service.findOne(1); + + expect(foundCat).toBeDefined(); + expect(foundCat).toEqual(createdCat); + }); + + it('should return undefined if cat is not found', () => { + const foundCat = service.findOne(999); + expect(foundCat).toBeUndefined(); + }); + }); +}); \ No newline at end of file diff --git a/sample/10-fastify/src/cats/cats.service.ts b/sample/10-fastify/src/cats/cats.service.ts index 2619cd7176d..ab265131cb7 100644 --- a/sample/10-fastify/src/cats/cats.service.ts +++ b/sample/10-fastify/src/cats/cats.service.ts @@ -1,15 +1,27 @@ import { Injectable } from '@nestjs/common'; import { Cat } from './interfaces/cat.interface'; +import { CreateCatDto } from './dto/create-cat.dto'; @Injectable() export class CatsService { private readonly cats: Cat[] = []; + private idCounter = 1; - create(cat: Cat) { - this.cats.push(cat); + create(createCatDto: CreateCatDto): Cat { + const newCat: Cat = { + id: this.idCounter++, + ...createCatDto, + }; + + this.cats.push(newCat); + return newCat; } findAll(): Cat[] { return this.cats; } -} + + findOne(id: number): Cat { + return this.cats.find((cat) => cat.id === id); + } +} \ No newline at end of file diff --git a/sample/10-fastify/src/cats/interfaces/cat.interface.ts b/sample/10-fastify/src/cats/interfaces/cat.interface.ts index b3e66c4b676..1374507c67b 100644 --- a/sample/10-fastify/src/cats/interfaces/cat.interface.ts +++ b/sample/10-fastify/src/cats/interfaces/cat.interface.ts @@ -1,4 +1,5 @@ export interface Cat { + readonly id: number; readonly name: string; readonly age: number; readonly breed: string;