diff --git a/README.md b/README.md index 4d18541..97606e3 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,7 @@ erDiagram - **컨트롤러 메서드**: HTTP 요청 중점 네이밍 - 생성: `create{Entity}` - 조회: `get{Entity}`, `get{Entities}` - - 업데이트: `patch{Entity}`, `put{Entity}` + - 업데이트: `patch{Entity}`, `put{Entity}` - 삭제: `delete{Entity}` - **서비스 메서드**: Entity 명시 - 생성: `create{Entity}` @@ -273,9 +273,26 @@ erDiagram ## 테스트 코드 네이밍 룰 -{메서드명}{기대결과}{테스트상태} +### jest 코드 -`isAdult_False_AgeLessThan18)` +```typescript +describe('{layer}', () => { + describe('{method}', async () => { + it('should {result}{condition}', () => {}); + }); +}); +``` + +### 예시 + +```typescript +AgreementsService + createAgreement + ✓ should return AgreementDto with valid input (6 ms) + existCheck + ✓ should throw exception when agreement does not exist (5 ms) + ✓ should return AgreementDto when agreement exists (2 ms) +``` ## 접두사 정리 diff --git a/package.json b/package.json index d046a7e..e881fcb 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "deploy:staging": "./deploy/deploy-staging.sh", "deploy:prod": "./deploy/deploy-prod.sh", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", - "test": "jest", + "test": "jest --verbose", "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", diff --git a/src/APIs/agreements/__test__/agreements.controller.spec.ts b/src/APIs/agreements/__test__/agreements.controller.spec.ts new file mode 100644 index 0000000..5614aca --- /dev/null +++ b/src/APIs/agreements/__test__/agreements.controller.spec.ts @@ -0,0 +1,114 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AgreementsService } from '../agreements.service'; +import { AgreementDto } from '../dtos/common/agreement.dto'; +import { + MockService, + MockServiceFactory, + TEST_DATE_FIELDS, +} from '@/utils/test.utils'; +import { AgreementType } from '@/common/enums/agreement-type.enum'; +import { AgreementsController } from '../agreements.controller'; +import { AgreementCreateRequestDto } from '../dtos/request/agreement-create-request.dto'; +import { Request } from 'express'; +import { AgreementPatchRequestDto } from '../dtos/request/agreement-patch-request.dto'; + +describe('AgreementsService', () => { + let ctrl_agreements: AgreementsController; + let svc_agreements: MockService; + let dto_agreement: AgreementDto; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [AgreementsController], + providers: [ + { + provide: AgreementsService, + useValue: MockServiceFactory.getMockService(AgreementsService), + }, + ], + }).compile(); + + ctrl_agreements = module.get(AgreementsController); + svc_agreements = + module.get>(AgreementsService); + dto_agreement = { + id: 1, + userId: 1, + agreementType: AgreementType.TERMS_OF_SERVICE, + isAgreed: true, + ...TEST_DATE_FIELDS, + }; + }); + + describe('agree', () => { + it('should return AgreementDto with valid input', async () => { + const req = { user: { userId: 1 } } as Request; + const dto: AgreementCreateRequestDto = { + agreementType: AgreementType.TERMS_OF_SERVICE, + isAgreed: true, + }; + svc_agreements.createAgreement.mockResolvedValue(dto_agreement); + + const result = await ctrl_agreements.agree(req, dto); + expect(result).toEqual(dto_agreement); + expect(svc_agreements.createAgreement).toHaveBeenCalledWith({ + ...dto, + userId: 1, + }); + }); + }); + + describe('fetchAgreements', () => { + it('should fetch agreements for the user', async () => { + const req = { user: { userId: 1 } } as Request; + svc_agreements.findAgreements.mockResolvedValue([dto_agreement]); + + const result = await ctrl_agreements.fetchAgreements(req); + expect(result).toEqual([dto_agreement]); + expect(svc_agreements.findAgreements).toHaveBeenCalledWith({ userId: 1 }); + }); + }); + + describe('fetchAgreementAdmin', () => { + it('should fetch agreements for the target user as admin', async () => { + const req = { user: { userId: 1 } } as Request; + const targetUserKakaoId = 2; + svc_agreements.adminCheck.mockResolvedValue(true); + svc_agreements.findAgreements.mockResolvedValue([dto_agreement]); + + const result = await ctrl_agreements.fetchAgreementAdmin( + req, + targetUserKakaoId, + ); + expect(result).toEqual([dto_agreement]); + expect(svc_agreements.adminCheck).toHaveBeenCalledWith({ userId: 1 }); + expect(svc_agreements.findAgreements).toHaveBeenCalledWith({ + userId: targetUserKakaoId, + }); + }); + }); + describe('patchAgreement', () => { + it('should patch an agreement', async () => { + const req = { user: { userId: 1 } } as Request; + const agreementId = 1; + const patchAgreementInput: AgreementPatchRequestDto = { isAgreed: false }; + const patchAgreementOutput: AgreementDto = { + ...dto_agreement, + isAgreed: patchAgreementInput.isAgreed, + }; + svc_agreements.patchAgreement.mockResolvedValue(patchAgreementOutput); + + const result = await ctrl_agreements.patchAgreement( + req, + agreementId, + patchAgreementInput, + ); + expect(result).toEqual(patchAgreementOutput); + expect(svc_agreements.patchAgreement).toHaveBeenCalledWith({ + ...patchAgreementInput, + agreementId, + userId: req.user.userId, + }); + }); + }); +}); diff --git a/src/APIs/agreements/__test__/agreements.service.spec.ts b/src/APIs/agreements/__test__/agreements.service.spec.ts index 62e1980..0768aab 100644 --- a/src/APIs/agreements/__test__/agreements.service.spec.ts +++ b/src/APIs/agreements/__test__/agreements.service.spec.ts @@ -54,131 +54,139 @@ describe('AgreementsService', () => { ...TEST_DATE_FIELDS, }; }); - - it('createAgreement_AgreementDto_ValidInput', async () => { - const createAgreementInput: IAgreementsServiceCreate = { - userId: 1, - agreementType: AgreementType.TERMS_OF_SERVICE, - isAgreed: true, - }; - const createAgreementOutput: AgreementDto = { - id: 1, - ...createAgreementInput, - ...TEST_DATE_FIELDS, - }; - repo_agreements.save.mockResolvedValue(createAgreementOutput); - const result = await svc_agreements.createAgreement(createAgreementInput); - expect(result).toEqual(createAgreementOutput); - expect(repo_agreements.save).toHaveBeenCalledWith(createAgreementInput); + describe('createAgreement', () => { + it('should return AgreementDto with valid input', async () => { + const createAgreementInput: IAgreementsServiceCreate = { + userId: 1, + agreementType: AgreementType.TERMS_OF_SERVICE, + isAgreed: true, + }; + const createAgreementOutput: AgreementDto = { + id: 1, + ...createAgreementInput, + ...TEST_DATE_FIELDS, + }; + repo_agreements.save.mockResolvedValue(createAgreementOutput); + const result = await svc_agreements.createAgreement(createAgreementInput); + expect(result).toEqual(createAgreementOutput); + expect(repo_agreements.save).toHaveBeenCalledWith(createAgreementInput); + }); }); - it('existCheck_ThrowError_NotExist', async () => { - const existCheckInput: IAgreementsServiceId = { agreementId: 1 }; - const findOneOutput: AgreementDto = null; - repo_agreements.findOne.mockResolvedValue(findOneOutput); - await expect(svc_agreements.existCheck(existCheckInput)).rejects.toThrow( - BlccuExceptionTest('AGREEMENT_NOT_FOUND'), - ); - expect(repo_agreements.findOne).toHaveBeenCalledWith({ - where: { id: existCheckInput.agreementId }, + describe('existCheck', () => { + it('should throw exception when agreement does not exist', async () => { + const existCheckInput: IAgreementsServiceId = { agreementId: 1 }; + const findOneOutput: AgreementDto = null; + repo_agreements.findOne.mockResolvedValue(findOneOutput); + await expect(svc_agreements.existCheck(existCheckInput)).rejects.toThrow( + BlccuExceptionTest('AGREEMENT_NOT_FOUND'), + ); + expect(repo_agreements.findOne).toHaveBeenCalledWith({ + where: { id: existCheckInput.agreementId }, + }); }); - }); - it('existCheck_AgreementDto_WhenExist', async () => { - const existCheckInput: IAgreementsServiceId = { agreementId: 1 }; - repo_agreements.findOne.mockResolvedValue(dto_agreement); - await expect(svc_agreements.existCheck(existCheckInput)).resolves.toEqual( - dto_agreement, - ); - expect(repo_agreements.findOne).toHaveBeenCalledWith({ - where: { id: existCheckInput.agreementId }, + it('should return AgreementDto when agreement exists', async () => { + const existCheckInput: IAgreementsServiceId = { agreementId: 1 }; + repo_agreements.findOne.mockResolvedValue(dto_agreement); + await expect(svc_agreements.existCheck(existCheckInput)).resolves.toEqual( + dto_agreement, + ); + expect(repo_agreements.findOne).toHaveBeenCalledWith({ + where: { id: existCheckInput.agreementId }, + }); }); }); - it('findAgreement_AgreementDto_ValidInput', async () => { - const findAgreementInput: IAgreementsServiceId = { agreementId: 1 }; - repo_agreements.findOne.mockResolvedValue(dto_agreement); - await expect( - svc_agreements.findAgreement(findAgreementInput), - ).resolves.toEqual(dto_agreement); - expect(repo_agreements.findOne).toHaveBeenCalledWith({ - where: { id: findAgreementInput.agreementId }, + describe('findAgreement', () => { + it('should return AgreementDto with valid input', async () => { + const findAgreementInput: IAgreementsServiceId = { agreementId: 1 }; + repo_agreements.findOne.mockResolvedValue(dto_agreement); + await expect( + svc_agreements.findAgreement(findAgreementInput), + ).resolves.toEqual(dto_agreement); + expect(repo_agreements.findOne).toHaveBeenCalledWith({ + where: { id: findAgreementInput.agreementId }, + }); }); }); - - it('findAgreements_AgreementDtos_ValidInput', async () => { - const findAgreementsInput: IAgreementsServiceUserId = { userId: 1 }; - repo_agreements.find.mockResolvedValue([dto_agreement]); - await expect( - svc_agreements.findAgreements(findAgreementsInput), - ).resolves.toEqual([dto_agreement]); - expect(repo_agreements.find).toHaveBeenCalledWith({ - where: { user: { id: findAgreementsInput.userId } }, + describe('findAgreements', () => { + it('should return AgreementDtos with valid input', async () => { + const findAgreementsInput: IAgreementsServiceUserId = { userId: 1 }; + repo_agreements.find.mockResolvedValue([dto_agreement]); + await expect( + svc_agreements.findAgreements(findAgreementsInput), + ).resolves.toEqual([dto_agreement]); + expect(repo_agreements.find).toHaveBeenCalledWith({ + where: { user: { id: findAgreementsInput.userId } }, + }); }); }); - it('patchAgreement_AgreementDto_ValidInput', async () => { - const patchAgreementInput: IAgreementsServicePatchAgreement = { - userId: 1, - agreementId: 1, - isAgreed: true, - }; - const saveOutput: AgreementDto = { - ...dto_agreement, - isAgreed: patchAgreementInput.isAgreed, - }; - repo_agreements.findOne.mockResolvedValue(dto_agreement); - repo_agreements.save.mockResolvedValue(saveOutput); - expect(await svc_agreements.patchAgreement(patchAgreementInput)).toEqual( - saveOutput, - ); - expect(repo_agreements.findOne).toHaveBeenCalledWith({ - where: { id: patchAgreementInput.agreementId }, + describe('patchAgreement', () => { + it('should return AgreementDto with valid input', async () => { + const patchAgreementInput: IAgreementsServicePatchAgreement = { + userId: 1, + agreementId: 1, + isAgreed: true, + }; + const saveOutput: AgreementDto = { + ...dto_agreement, + isAgreed: patchAgreementInput.isAgreed, + }; + repo_agreements.findOne.mockResolvedValue(dto_agreement); + repo_agreements.save.mockResolvedValue(saveOutput); + expect(await svc_agreements.patchAgreement(patchAgreementInput)).toEqual( + saveOutput, + ); + expect(repo_agreements.findOne).toHaveBeenCalledWith({ + where: { id: patchAgreementInput.agreementId }, + }); + expect(repo_agreements.save).toHaveBeenCalledWith({ + ...dto_agreement, + isAgreed: patchAgreementInput.isAgreed, + }); }); - expect(repo_agreements.save).toHaveBeenCalledWith({ - ...dto_agreement, - isAgreed: patchAgreementInput.isAgreed, - }); - }); - it('patchAgreement_AgreementDto_InvalidAgreementId', async () => { - const patchAgreementInput: IAgreementsServicePatchAgreement = { - userId: 1, - agreementId: 1, - isAgreed: true, - }; - const findOneOutput: AgreementDto = null; - const saveOutput: AgreementDto = { - ...findOneOutput, - isAgreed: patchAgreementInput.isAgreed, - }; - repo_agreements.findOne.mockResolvedValue(findOneOutput); - repo_agreements.save.mockResolvedValue(saveOutput); - await expect( - svc_agreements.patchAgreement(patchAgreementInput), - ).rejects.toThrow(BlccuExceptionTest('AGREEMENT_NOT_FOUND')); - expect(repo_agreements.findOne).toHaveBeenCalledWith({ - where: { id: patchAgreementInput.agreementId }, + it('should throw exception for invalid agreementId', async () => { + const patchAgreementInput: IAgreementsServicePatchAgreement = { + userId: 1, + agreementId: 1, + isAgreed: true, + }; + const findOneOutput: AgreementDto = null; + const saveOutput: AgreementDto = { + ...findOneOutput, + isAgreed: patchAgreementInput.isAgreed, + }; + repo_agreements.findOne.mockResolvedValue(findOneOutput); + repo_agreements.save.mockResolvedValue(saveOutput); + await expect( + svc_agreements.patchAgreement(patchAgreementInput), + ).rejects.toThrow(BlccuExceptionTest('AGREEMENT_NOT_FOUND')); + expect(repo_agreements.findOne).toHaveBeenCalledWith({ + where: { id: patchAgreementInput.agreementId }, + }); }); - }); - it('patchAgreement_AgreementDto_InvalidUserId', async () => { - const patchAgreementInput: IAgreementsServicePatchAgreement = { - userId: 2, - agreementId: 1, - isAgreed: true, - }; - const saveOutput: AgreementDto = { - ...dto_agreement, - isAgreed: patchAgreementInput.isAgreed, - }; - repo_agreements.findOne.mockResolvedValue(dto_agreement); - repo_agreements.save.mockResolvedValue(saveOutput); - await expect( - svc_agreements.patchAgreement(patchAgreementInput), - ).rejects.toThrow(BlccuExceptionTest('NOT_THE_OWNER')); - expect(repo_agreements.findOne).toHaveBeenCalledWith({ - where: { id: patchAgreementInput.agreementId }, + it('should throw exception for invalid userId', async () => { + const patchAgreementInput: IAgreementsServicePatchAgreement = { + userId: 2, + agreementId: 1, + isAgreed: true, + }; + const saveOutput: AgreementDto = { + ...dto_agreement, + isAgreed: patchAgreementInput.isAgreed, + }; + repo_agreements.findOne.mockResolvedValue(dto_agreement); + repo_agreements.save.mockResolvedValue(saveOutput); + await expect( + svc_agreements.patchAgreement(patchAgreementInput), + ).rejects.toThrow(BlccuExceptionTest('NOT_THE_OWNER')); + expect(repo_agreements.findOne).toHaveBeenCalledWith({ + where: { id: patchAgreementInput.agreementId }, + }); }); }); }); diff --git a/src/APIs/agreements/agreements.repository.ts b/src/APIs/agreements/agreements.repository.ts index 5804cb9..b2a30cb 100644 --- a/src/APIs/agreements/agreements.repository.ts +++ b/src/APIs/agreements/agreements.repository.ts @@ -7,7 +7,4 @@ export class AgreementsRepository extends Repository { constructor(private db_dataSource: DataSource) { super(Agreement, db_dataSource.createEntityManager()); } - getHello(): string { - return '1'; - } } diff --git a/src/utils/test.utils.ts b/src/utils/test.utils.ts index 1db53f8..e8d6c46 100644 --- a/src/utils/test.utils.ts +++ b/src/utils/test.utils.ts @@ -5,6 +5,10 @@ export type MockRepository = { [P in keyof T]?: jest.Mock; }; +export type MockService = { + [P in keyof T]?: jest.Mock; +}; + export class MockRepositoryFactory { static getMockRepository( type: new (...args: any[]) => T, @@ -34,3 +38,18 @@ export const TEST_DATE_FIELDS = { dateUpdated: expect.any(Date), dateDeleted: expect.any(Date), }; + +export class MockServiceFactory { + static getMockService(type: new (...args: any[]) => T): MockService { + const mockService: MockService = {}; + + // 서비스 클래스의 메서드를 jest.fn()으로 대체 + Object.getOwnPropertyNames(type.prototype) + .filter((key: string) => key !== 'constructor') + .forEach((key: string) => { + mockService[key] = jest.fn(); + }); + + return mockService; + } +}