From 566bf003861a4eeb775e998976116e166370da77 Mon Sep 17 00:00:00 2001 From: Olamideod-tech Date: Wed, 30 Oct 2024 11:54:30 +0100 Subject: [PATCH 1/2] Unit test: created test for user controller --- package-lock.json | 21 +++- package.json | 2 +- src/users/users.controller.spec.ts | 196 ++++++++++++++++++++++++++++- 3 files changed, 209 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index de1e8d3..c078e10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,7 @@ "@faker-js/faker": "^8.1.0", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", - "@nestjs/testing": "^10.0.0", + "@nestjs/testing": "^10.4.5", "@types/bcryptjs": "^2.4.4", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", @@ -67,6 +67,9 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" + }, + "engines": { + "node": ">=18.12.1" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -3811,12 +3814,13 @@ } }, "node_modules/@nestjs/testing": { - "version": "10.3.7", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.7.tgz", - "integrity": "sha512-PmwZXyoCC/m3F3IFgpgD+SNN6cDPQa/vi3YQxFruvfX3cuHq+P6ZFvBB7hwaKKsLlhA0so42LsMm41oFBkdouw==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.5.tgz", + "integrity": "sha512-3NhmztE+fK3MuuOZhXihvMIhxm0QuDM2BneHvM5A0oVLG+STsAeGBqbDr/Ef2qsvqH5HaqvfGbVJ4N1DQnZE5A==", "dev": true, + "license": "MIT", "dependencies": { - "tslib": "2.6.2" + "tslib": "2.7.0" }, "funding": { "type": "opencollective", @@ -3837,6 +3841,13 @@ } } }, + "node_modules/@nestjs/testing/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true, + "license": "0BSD" + }, "node_modules/@nestjs/throttler": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-5.1.2.tgz", diff --git a/package.json b/package.json index a4645dd..253b3ee 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "@faker-js/faker": "^8.1.0", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", - "@nestjs/testing": "^10.0.0", + "@nestjs/testing": "^10.4.5", "@types/bcryptjs": "^2.4.4", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts index 5778d34..f4a1dab 100644 --- a/src/users/users.controller.spec.ts +++ b/src/users/users.controller.spec.ts @@ -1,22 +1,210 @@ import { Test, TestingModule } from '@nestjs/testing'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; -import { TestModule } from 'src/shared/testkits'; +import { JwtUsersGuard } from 'src/shared/auth/guards/jwt.users.guard'; +import { + NotFoundException, + InternalServerErrorException, +} from '@nestjs/common'; +import { CreateUserDto } from 'src/shared/dtos/create-user.dto'; +import { UserChangePasswordDto } from './dto/user-change-password.dto'; +import { TempLeadDto } from './dto/temp-lead.dto'; +import { RequestReactivationDto } from './dto/request-reactivation.dto'; +import { RegistrationMethod } from 'src/shared/interfaces'; describe('UsersController', () => { let controller: UsersController; + let service: UsersService; + + const mockUser = { _id: '123', email: 'user@example.com' }; + + const mockUsersService = { + findMe: jest.fn().mockResolvedValue(mockUser), + changePassword: jest.fn(), + findAll: jest.fn().mockResolvedValue([mockUser]), + findById: jest.fn().mockResolvedValue(mockUser), + findByEmail: jest.fn().mockResolvedValue(mockUser), + update: jest.fn(), + addPhoto: jest.fn(), + createTempRegistration: jest.fn().mockResolvedValue('temp-registration-id'), + createUser: jest.fn().mockResolvedValue(mockUser), + requestVerification: jest.fn(), + deactivateAccount: jest.fn(), + requestReactivation: jest.fn(), + paraseEncryptedParams: jest.fn().mockReturnValue({ + userId: '123', + email: 'user@example.com', + }), + }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - imports: [TestModule], controllers: [UsersController], - providers: [UsersService], - }).compile(); + providers: [ + { + provide: UsersService, + useValue: mockUsersService, + }, + ], + }) + .overrideGuard(JwtUsersGuard) + .useValue({ canActivate: () => true }) + .compile(); controller = module.get(UsersController); + service = module.get(UsersService); + }); + + afterEach(() => { + jest.clearAllMocks(); // Clear mock calls between tests }); it('should be defined', () => { expect(controller).toBeDefined(); }); + + describe('myProfile', () => { + it('should return the user profile', async () => { + const req = { user: { _id: '123' } }; + const result = await controller.myProfile(req); + expect(result).toEqual(mockUser); + expect(service.findMe).toHaveBeenCalled(); + }); + }); + + describe('findAll', () => { + it('should return an array of users', async () => { + const req = {}; + const result = await controller.findAll(req); + expect(result).toEqual([mockUser]); + expect(service.findAll).toHaveBeenCalled(); + }); + }); + + describe('findOne', () => { + it('should return a user by ID', async () => { + const result = await controller.findOne('123'); + expect(result).toEqual(mockUser); + expect(service.findById).toHaveBeenCalledWith('123'); + }); + + it('should throw NotFoundException if user is not found', async () => { + jest.spyOn(service, 'findById').mockResolvedValueOnce(null); + await expect(controller.findOne('invalid-id')).rejects.toThrow( + NotFoundException, + ); + }); + }); + + describe('findByUsername', () => { + it('should return true if user exists', async () => { + const result = await controller.findByUsername('user@example.com'); + expect(result).toBe(true); + expect(service.findByEmail).toHaveBeenCalledWith('user@example.com'); + }); + + it('should return false if user does not exist', async () => { + jest.spyOn(service, 'findByEmail').mockResolvedValueOnce(null); + const result = await controller.findByUsername('nonexistent@example.com'); + expect(result).toBe(false); + }); + }); + + describe('changePassword', () => { + it('should call changePassword with correct arguments', async () => { + const req = { user: { _id: '123' } }; + const payload: UserChangePasswordDto = { + oldPassword: 'old', + newPassword: 'new', + confirmPassword: 'new', + }; + + await controller.changePassword(req, payload); + expect(service.changePassword).toHaveBeenCalledWith(req, '123', payload); + }); + }); + + describe('createLead', () => { + it('should create a temporary lead registration', async () => { + const payload: TempLeadDto = { + email: 'lead@gmail.com', + leadPosition: 'manager', + firstName: 'Dennis', + lastName: 'Dennis', + createdAt: new Date(), + }; + const result = await controller.createLead(payload); + expect(result).toBe('temp-registration-id'); + }); + }); + + describe('register', () => { + it('should redirect to new user form if userId is not found', async () => { + mockUsersService.paraseEncryptedParams.mockReturnValueOnce({ + email: 'lead@example.com', + }); + const result = await controller.register('encrypted-data'); + expect(result).toEqual({ + url: '/leads/new-user-form?email=lead@example.com', + }); + }); + + it('should throw NotFoundException for invalid link', async () => { + mockUsersService.paraseEncryptedParams.mockImplementationOnce(() => { + throw new Error(); + }); + await expect(controller.register('invalid-data')).rejects.toThrow( + NotFoundException, + ); + }); + }); + + describe('newUserForm', () => { + it('should create a new user and return redirect URL', async () => { + const payload: CreateUserDto = { + email: 'new@example.com', + password: 'password', + joinMethod: RegistrationMethod.SIGN_UP, + firstName: 'Dennis', + lastName: 'Dennis', + }; + const result = await controller.newUserForm(payload); + expect(result).toEqual({ + url: '/leads/create?email=new@example.com', + }); + }); + + it('should throw InternalServerErrorException on failure', async () => { + jest.spyOn(service, 'createUser').mockImplementationOnce(() => { + throw new Error(); + }); + await expect( + controller.newUserForm({ + email: '', + password: '', + lastName: '', + firstName: '', + joinMethod: RegistrationMethod.SIGN_UP, + }), + ).rejects.toThrow(InternalServerErrorException); + }); + }); + + describe('deactivateAccount', () => { + it('should call deactivateAccount with correct arguments', async () => { + const req = { user: { _id: '123' } }; + const payload = { reason: 'No longer needed' }; + + await controller.deactivateAccount(req, '123', payload); + expect(service.deactivateAccount).toHaveBeenCalledWith('123'); + }); + }); + + describe('requestReactivation', () => { + it('should call requestReactivation with correct arguments', async () => { + const payload: RequestReactivationDto = { message: 'Please reactivate' }; + await controller.requestReactivation('123', payload); + expect(service.requestReactivation).toHaveBeenCalledWith('123'); + }); + }); }); From 75bdf583f6a8bc040552e2742618001a168e0b4f Mon Sep 17 00:00:00 2001 From: DennisTemoye Date: Wed, 20 Nov 2024 19:43:10 +0100 Subject: [PATCH 2/2] new:created unit test for user module --- src/users/users.controller.spec.ts | 215 +++++++++++++++++++++++++++++ src/users/users.controller.ts | 11 +- 2 files changed, 223 insertions(+), 3 deletions(-) diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts index 90fe441..51171cd 100644 --- a/src/users/users.controller.spec.ts +++ b/src/users/users.controller.spec.ts @@ -10,6 +10,15 @@ import { UserRole, UserStatus, } from 'src/shared/interfaces'; +import { JwtUsersGuard } from 'src/shared/auth/guards/jwt.users.guard'; +import { RequestReactivationDto } from './dto/request-reactivation.dto'; +import { + InternalServerErrorException, + NotFoundException, +} from '@nestjs/common'; +import { UserChangePasswordDto } from './dto/user-change-password.dto'; +import { TempLeadDto } from './dto/temp-lead.dto'; +import { CreateUserDto } from 'src/shared/dtos/create-user.dto'; describe('UsersAdminController', () => { let controller: UsersController; @@ -95,3 +104,209 @@ const createUserMock = (overrides: Partial = {}): User => { ...overrides, // Overrides will allow you to customize the mock as needed }; }; +describe('UsersController', () => { + let controller: UsersController; + let service: UsersService; + + const mockUser = { _id: '123', email: 'tdennis.developer@gmail.com' }; + + const mockUsersService = { + findMe: jest.fn().mockResolvedValue(mockUser), + changePassword: jest.fn(), + findAll: jest.fn().mockResolvedValue([mockUser]), + findById: jest.fn().mockResolvedValue(mockUser), + findByEmail: jest.fn().mockResolvedValue(mockUser), + update: jest.fn(), + addPhoto: jest.fn(), + createTempRegistration: jest.fn().mockResolvedValue('temp-registration-id'), + createUser: jest.fn().mockResolvedValue(mockUser), + requestVerification: jest.fn(), + deactivateAccount: jest.fn(), + requestReactivation: jest.fn(), + paraseEncryptedParams: jest.fn().mockReturnValue({ + userId: '6706619dbee933e796f61484', + email: 'tdennis.developer@gmail.com', + }), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [UsersController], + providers: [ + { + provide: UsersService, + useValue: mockUsersService, + }, + ], + }) + .overrideGuard(JwtUsersGuard) + .useValue({ canActivate: () => true }) + .compile(); + + controller = module.get(UsersController); + service = module.get(UsersService); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + // Add the additional describe blocks from the second code here, as-is. + describe('myProfile', () => { + it('should return the user profile', async () => { + const req = { user: { _id: '123' } }; + const result = await controller.myProfile(req); + expect(result).toEqual(mockUser); + expect(service.findMe).toHaveBeenCalled(); + }); + }); + + describe('findAll', () => { + it('should return an array of users', async () => { + const req = {}; + const result = await controller.findAll(req); + expect(result).toEqual([mockUser]); + expect(service.findAll).toHaveBeenCalled(); + }); + }); + + describe('findOne', () => { + it('should return a user by ID', async () => { + const result = await controller.findOne('6706619dbee933e796f61484'); + expect(result).toEqual(mockUser); + expect(service.findById).toHaveBeenCalledWith('6706619dbee933e796f61484'); + }); + + it('should throw NotFoundException if user is not found', async () => { + jest.spyOn(service, 'findById').mockResolvedValueOnce(null); + + await expect(controller.findOne('invalid-id')).rejects.toThrow( + NotFoundException, + ); + }); + }); + + describe('findByUsername', () => { + it('should return true if user exists', async () => { + const result = await controller.findByUsername( + 'tdennis.developer@gmail.com', + ); + expect(result).toBe(true); + expect(service.findByEmail).toHaveBeenCalledWith( + 'tdennis.developer@gmail.com', + ); + }); + + it('should return false if user does not exist', async () => { + jest.spyOn(service, 'findByEmail').mockResolvedValueOnce(null); + const result = await controller.findByUsername('nonexistent@example.com'); + expect(result).toBe(false); + }); + }); + + describe('changePassword', () => { + it('should call changePassword with correct arguments', async () => { + const req = { user: { _id: '6706619dbee933e796f61484' } }; + const payload: UserChangePasswordDto = { + oldPassword: 'old', + newPassword: 'new', + confirmPassword: 'new', + }; + + await controller.changePassword(req, payload); + expect(service.changePassword).toHaveBeenCalledWith( + req, + '6706619dbee933e796f61484', + payload, + ); + }); + }); + + describe('createLead', () => { + it('should create a temporary lead registration', async () => { + const payload: TempLeadDto = { + email: 'lead@gmail.com', + leadPosition: 'manager', + firstName: 'Dennis', + lastName: 'Dennis', + createdAt: new Date(), + }; + const result = await controller.createLead(payload); + expect(result).toBe('temp-registration-id'); + }); + }); + + describe('register', () => { + it('should redirect to new user form if userId is not found', async () => { + mockUsersService.paraseEncryptedParams.mockReturnValueOnce({ + email: 'tdennis.developer@gmail.com', + }); + const result = await controller.register('encrypted-data'); + expect(result).toEqual({ + url: `/leads/new-user-form?email=tdennis.developer%40gmail.com`, + }); + }); + + it('should throw NotFoundException for invalid link', async () => { + mockUsersService.paraseEncryptedParams.mockImplementationOnce(() => { + throw new Error(); + }); + await expect(controller.register('invalid-data')).rejects.toThrow( + NotFoundException, + ); + }); + }); + + describe('newUserForm', () => { + it('should create a new user and return redirect URL', async () => { + const payload: CreateUserDto = { + email: 'tdennis.developer@gmail.com', + password: 'password', + joinMethod: RegistrationMethod.SIGN_UP, + firstName: 'Dennis', + lastName: 'Dennis', + }; + const result = await controller.newUserForm(payload); + expect(result).toEqual({ + url: `/leads/create?email=${payload.email}`, + }); + }); + + it('should throw InternalServerErrorException on failure', async () => { + jest.spyOn(service, 'createUser').mockImplementationOnce(() => { + throw new Error(); + }); + await expect( + controller.newUserForm({ + email: '', + password: '', + lastName: '', + firstName: '', + joinMethod: RegistrationMethod.SIGN_UP, + }), + ).rejects.toThrow(InternalServerErrorException); + }); + }); + + describe('deactivateAccount', () => { + it('should call deactivateAccount with correct arguments', async () => { + const req = { user: { _id: '123' } }; + const payload = { reason: 'No longer needed' }; + + await controller.deactivateAccount(req, '123', payload); + expect(service.deactivateAccount).toHaveBeenCalledWith('123'); + }); + }); + + describe('requestReactivation', () => { + it('should call requestReactivation with correct arguments', async () => { + const payload: RequestReactivationDto = { message: 'Please reactivate' }; + await controller.requestReactivation('123', payload); + expect(service.requestReactivation).toHaveBeenCalledWith('123'); + }); + }); +}); diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 6df217c..7868509 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -30,7 +30,8 @@ import { CreateUserDto } from 'src/shared/dtos/create-user.dto'; import { UserChangePasswordDto } from './dto/user-change-password.dto'; import { UserAddPhotoDto } from './dto/user-add-photo.dto'; import { DeactivateAccountDto } from './dto/deactivate-account.dto'; -import { RequestReactivationDto } from './dto/request-reactivation.dto';import { TempLeadDto } from './dto/temp-lead.dto'; +import { RequestReactivationDto } from './dto/request-reactivation.dto'; +import { TempLeadDto } from './dto/temp-lead.dto'; @ApiTags('users') @Controller('users') @@ -104,8 +105,12 @@ export class UsersController { } @Get(':id') - findOne(@Param('id') id: string) { - return this.usersService.findById(id); + async findOne(@Param('id') id: string) { + const user = await this.usersService.findById(id); + if (!user) { + throw new NotFoundException(`User with ID ${id} not found`); + } + return user; } @ApiBearerAuth()