From 5c83799e84b566e8a4d384d65dccbbfc8ec192d4 Mon Sep 17 00:00:00 2001 From: cabbage556 Date: Sun, 2 Apr 2023 21:49:59 +0900 Subject: [PATCH 1/2] =?UTF-8?q?test:=20ReservationsResolver=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - reservation.resolver.spec.ts 파일 추가 - reservation.mocking.ts 파일 추가 test-#162 --- .../__test__/reservation.mocking.ts | 34 +++++++++ .../__test__/reservation.resolver.spec.ts | 75 +++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/apis/reservations/__test__/reservation.mocking.ts create mode 100644 src/apis/reservations/__test__/reservation.resolver.spec.ts diff --git a/src/apis/reservations/__test__/reservation.mocking.ts b/src/apis/reservations/__test__/reservation.mocking.ts new file mode 100644 index 0000000..e0f2dd1 --- /dev/null +++ b/src/apis/reservations/__test__/reservation.mocking.ts @@ -0,0 +1,34 @@ +export const MOCK_RESERVATION = { + id: '6b7a693d-f0db-4e98-bddd-d855f5324ecb', + date: new Date('2023-03-31'), + time: '19:00', + createdAt: '2023-03-30 00:55:19.960721', + deletedAt: null, + shop: { + id: '89a3c0c1-60db-4725-8e65-b33a95acb800', + }, + user: { + id: 'c7fb31dd-a6d1-4d9a-9b91-d3dd9e40f8c8', + }, + dog: { + id: '64acf5f8-95bf-4256-8a23-8b073fb02c42', + }, + review: { + id: '9cf2e431-70dd-44ca-ad9c-8735d594fc0a', + }, +}; + +export const MOCK_SHOP = { + id: '89a3c0c1-60db-4725-8e65-b33a95acb800', + name: '서현펫', + phone: '01012341234', + openHour: '11:00', + closeHour: '21:00', + address: '서울 양천구 목동 739-11 1층', + code: 11150, + lat: 37.5382114, + lng: 126.866754, + averageStar: 5, + updatedAt: '2023-03-30 01:03:12', + deletedAt: null, +}; diff --git a/src/apis/reservations/__test__/reservation.resolver.spec.ts b/src/apis/reservations/__test__/reservation.resolver.spec.ts new file mode 100644 index 0000000..c3bb05c --- /dev/null +++ b/src/apis/reservations/__test__/reservation.resolver.spec.ts @@ -0,0 +1,75 @@ +import { + ConflictException, + NotFoundException, + UnprocessableEntityException, +} from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { DogsService } from 'src/apis/dogs/dogs.service'; +import { ShopsService } from 'src/apis/shops/shops.service'; +import { UsersService } from 'src/apis/users/user.service'; +import { CreateReservationInput } from '../dto/create-reservation.input'; +import { ReservationsResolver } from '../reservation.resolver'; +import { ReservationsService } from '../reservation.service'; +import { MOCK_RESERVATION, MOCK_SHOP } from './reservation.mocking'; + +describe('ReservationsResolver', () => { + let reservationsResolver: ReservationsResolver; + + const mockReservationsService = { + create: jest.fn(), + checkDuplication: jest.fn(), + }; + + const mockShopsService = { + findById: jest.fn(), + }; + + const mockUsersService = { + findOne: jest.fn(), + }; + + const mockDogsService = { + findOneById: jest.fn(), + }; + + beforeEach(async () => { + const reservationsModule = await Test.createTestingModule({ + providers: [ + ReservationsResolver, + { + provide: ReservationsService, + useValue: mockReservationsService, + }, + { + provide: ShopsService, + useValue: mockShopsService, + }, + { + provide: UsersService, + useValue: mockUsersService, + }, + { + provide: DogsService, + useValue: mockDogsService, + }, + ], + }).compile(); + + reservationsResolver = + reservationsModule.get(ReservationsResolver); + }); + + it('reservationsResolver가 정의되어야 함', () => { + expect(reservationsResolver).toBeDefined(); + }); + + describe('createReservation', () => { + const createReservationInput: CreateReservationInput = { + date: MOCK_RESERVATION.date, + time: MOCK_RESERVATION.time, + shopId: MOCK_RESERVATION.shop.id, + userId: MOCK_RESERVATION.user.id, + dogId: MOCK_RESERVATION.dog.id, + }; + }); +}); From 7b8ff758e6c3b7bcff21a8930425695b70e2ca3d Mon Sep 17 00:00:00 2001 From: cabbage556 Date: Mon, 3 Apr 2023 10:04:41 +0900 Subject: [PATCH 2/2] =?UTF-8?q?teet:=20ReservationsResolver=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fetchForShopDetailPage API 제외 test-#162 --- .../__test__/reservation.mocking.ts | 12 +- .../__test__/reservation.resolver.spec.ts | 289 +++++++++++++++++- 2 files changed, 297 insertions(+), 4 deletions(-) diff --git a/src/apis/reservations/__test__/reservation.mocking.ts b/src/apis/reservations/__test__/reservation.mocking.ts index e0f2dd1..5ac9095 100644 --- a/src/apis/reservations/__test__/reservation.mocking.ts +++ b/src/apis/reservations/__test__/reservation.mocking.ts @@ -13,9 +13,6 @@ export const MOCK_RESERVATION = { dog: { id: '64acf5f8-95bf-4256-8a23-8b073fb02c42', }, - review: { - id: '9cf2e431-70dd-44ca-ad9c-8735d594fc0a', - }, }; export const MOCK_SHOP = { @@ -32,3 +29,12 @@ export const MOCK_SHOP = { updatedAt: '2023-03-30 01:03:12', deletedAt: null, }; + +export const MOCK_REVIEW = { + id: '3f50f151-55ba-4e1b-a5dc-8f08f2bc9394', + contents: '너무 이쁘게 잘라주세요', + createdAt: '2023-03-30 01:03:12.261083', + star: 4, + reservationId: 'e9b15e1d-a71b-4dcf-841f-16f99f6f91f8', + shopId: '89a3c0c1-60db-4725-8e65-b33a95acb800', +}; diff --git a/src/apis/reservations/__test__/reservation.resolver.spec.ts b/src/apis/reservations/__test__/reservation.resolver.spec.ts index c3bb05c..c1a9c3d 100644 --- a/src/apis/reservations/__test__/reservation.resolver.spec.ts +++ b/src/apis/reservations/__test__/reservation.resolver.spec.ts @@ -4,13 +4,24 @@ import { UnprocessableEntityException, } from '@nestjs/common'; import { Test } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; import { DogsService } from 'src/apis/dogs/dogs.service'; +import { MOCK_DOG, MOCK_USER } from 'src/apis/dogs/__test__/dogs.mocking'; import { ShopsService } from 'src/apis/shops/shops.service'; import { UsersService } from 'src/apis/users/user.service'; import { CreateReservationInput } from '../dto/create-reservation.input'; +import { Reservation } from '../entities/reservation.entity'; import { ReservationsResolver } from '../reservation.resolver'; import { ReservationsService } from '../reservation.service'; -import { MOCK_RESERVATION, MOCK_SHOP } from './reservation.mocking'; +import { + MOCK_RESERVATION, + MOCK_REVIEW, + MOCK_SHOP, +} from './reservation.mocking'; +import * as httpMocks from 'node-mocks-http'; +import { IContext } from 'src/commons/interface/context'; +import { User } from 'src/apis/users/entities/user.entity'; +import { Review } from 'src/apis/reviews/entities/review.entity'; describe('ReservationsResolver', () => { let reservationsResolver: ReservationsResolver; @@ -18,6 +29,17 @@ describe('ReservationsResolver', () => { const mockReservationsService = { create: jest.fn(), checkDuplication: jest.fn(), + findOne: jest.fn(), + findAllByUserId: jest.fn(), + findAllByShopId: jest.fn(), + delete: jest.fn(), + }; + + const mockReservationsRepository = { + save: jest.fn(), + findOne: jest.fn(), + find: jest.fn(), + softDelete: jest.fn(), }; const mockShopsService = { @@ -32,6 +54,10 @@ describe('ReservationsResolver', () => { findOneById: jest.fn(), }; + const mockReviewRepository = { + find: jest.fn(), + }; + beforeEach(async () => { const reservationsModule = await Test.createTestingModule({ providers: [ @@ -40,6 +66,10 @@ describe('ReservationsResolver', () => { provide: ReservationsService, useValue: mockReservationsService, }, + { + provide: getRepositoryToken(Reservation), + useValue: mockReservationsRepository, + }, { provide: ShopsService, useValue: mockShopsService, @@ -52,6 +82,10 @@ describe('ReservationsResolver', () => { provide: DogsService, useValue: mockDogsService, }, + { + provide: getRepositoryToken(Review), + useValue: mockReviewRepository, + }, ], }).compile(); @@ -71,5 +105,258 @@ describe('ReservationsResolver', () => { userId: MOCK_RESERVATION.user.id, dogId: MOCK_RESERVATION.dog.id, }; + + it('예약 시간이 중복된 경우 ConflictException을 던져야 함', () => { + mockReservationsService.checkDuplication.mockImplementation( + (date: Date, time: string, shopId: string) => MOCK_RESERVATION, + ); + + try { + reservationsResolver.createReservation(createReservationInput); + } catch (error) { + expect(error).toBeInstanceOf(ConflictException); + expect(error.message).toBe('이미 예약된 시간입니다'); + } + }); + + it('유효하지 않은 shopId인 경우 UnprocessableEntityException를 던져야 함', () => { + mockReservationsService.checkDuplication.mockImplementation( + (date: Date, time: string, shopId: string) => null, + ); + mockShopsService.findById.mockImplementation((shopId: string) => null); + + try { + reservationsResolver.createReservation(createReservationInput); + } catch (error) { + expect(error).toBeInstanceOf(UnprocessableEntityException); + expect(error.message).toBe('유효하지 않은 가게ID 입니다'); + } + }); + + it('유효하지 않은 userId인 경우 UnprocessableEntityException를 던져야 함', () => { + mockReservationsService.checkDuplication.mockImplementation( + (date: Date, time: string, shopId: string) => null, + ); + mockShopsService.findById.mockImplementation( + (shopId: string) => MOCK_SHOP, + ); + mockUsersService.findOne.mockImplementation((userId: string) => null); + + try { + reservationsResolver.createReservation(createReservationInput); + } catch (error) { + expect(error).toBeInstanceOf(UnprocessableEntityException); + expect(error.message).toBe('유효하지 않은 회원ID 입니다'); + } + }); + + it('강아지 정보를 찾을 수 없는 경우 NotFoundException을 던져야 함', () => { + mockReservationsService.checkDuplication.mockImplementation( + (date: Date, time: string, shopId: string) => null, + ); + mockShopsService.findById.mockImplementation( + (shopId: string) => MOCK_SHOP, + ); + mockUsersService.findOne.mockImplementation( + (userId: string) => MOCK_USER, + ); + mockDogsService.findOneById.mockImplementation((dogId: string) => { + throw new NotFoundException(`id ${dogId}를 갖는 강아지를 찾을 수 없음`); + }); + + try { + reservationsResolver.createReservation(createReservationInput); + } catch (error) { + expect(error).toBeInstanceOf(NotFoundException); + expect(error.message).toBe( + `id ${createReservationInput.dogId}를 갖는 강아지를 찾을 수 없음`, + ); + } + }); + + it('생성한 예약 정보를 리턴해야 함', async () => { + mockReservationsService.checkDuplication.mockImplementation( + (date: Date, time: string, shopId: string) => null, + ); + mockShopsService.findById.mockImplementation( + (shopId: string) => MOCK_SHOP, + ); + mockUsersService.findOne.mockImplementation( + (userId: string) => MOCK_USER, + ); + mockDogsService.findOneById.mockImplementation( + (dogId: string) => MOCK_DOG, + ); + mockReservationsRepository.save.mockImplementation( + ( + date: Date, + time: string, + shop: { id: string }, + user: { id: string }, + dog: { id: string }, + ) => MOCK_RESERVATION, + ); + mockReservationsRepository.findOne.mockImplementation( + (where: { id: string }) => MOCK_RESERVATION, + ); + mockReservationsService.create.mockImplementation( + (createReservationInput: CreateReservationInput) => MOCK_RESERVATION, + ); + + const result = await reservationsResolver.createReservation( + createReservationInput, + ); + expect(result).toEqual(MOCK_RESERVATION); + }); + }); + + describe('fetchReservation', () => { + it('예약을 찾을 수 없는 경우 UnprocessableEntityException를 던져야 함', () => { + mockReservationsRepository.findOne.mockImplementation( + (where: { id: string }) => null, + ); + const invalidReservationId = '6b7a693d-f0db-4e98-bddd-d855f5324ecf'; + + try { + reservationsResolver.fetchReservation(invalidReservationId); + } catch (error) { + expect(error).toBeInstanceOf(UnprocessableEntityException); + expect(error.message).toBe('예약을 찾을 수 없습니다'); + } + }); + + it('예약 정보를 리턴해야 함', async () => { + mockReservationsRepository.findOne.mockImplementation( + (where: { id: string }) => MOCK_RESERVATION, + ); + mockReservationsService.findOne.mockImplementation( + (reservationId: string) => MOCK_RESERVATION, + ); + + const result = await reservationsResolver.fetchReservation( + MOCK_RESERVATION.id, + ); + expect(result).toEqual(MOCK_RESERVATION); + }); + }); + + describe('fetchReservationsByUser', () => { + const context: IContext = { + req: httpMocks.createRequest(), + res: httpMocks.createResponse(), + }; + context.req.user = new User(); + + it('회원을 찾을 수 없는 경우 UnprocessableEntityException를 던져야 함', () => { + mockReservationsRepository.find.mockImplementation( + (where: { user: { id: string } }) => null, + ); + const invalidUserId = 'c84fa63e-7a05-4cd5-b015-d4db9a262b20'; + context.req.user.id = invalidUserId; + + try { + reservationsResolver.fetchReservationsByUser(context); + } catch (error) { + expect(error).toBeInstanceOf(UnprocessableEntityException); + expect(error.message).toBe( + `회원ID가 ${invalidUserId}인 예약을 찾을 수 없습니다`, + ); + } + }); + + it('회원의 모든 예약 정보를 배열로 리턴해야 함', async () => { + mockReservationsRepository.find.mockImplementation( + (where: { user: { id: string } }) => [MOCK_RESERVATION], + ); + mockReservationsService.findAllByUserId.mockImplementation( + (context: IContext) => [MOCK_RESERVATION], + ); + + const result = await reservationsResolver.fetchReservationsByUser( + context, + ); + expect(result).toEqual([MOCK_RESERVATION]); + }); + }); + + describe('fetchReservationsByShop', () => { + it('가게를 찾을 수 없는 경우 UnprocessableEntityException를 던져야 함', () => { + mockReservationsRepository.find.mockImplementation( + (where: { shop: { id: string } }) => null, + ); + const invalidShopId = '89a3c0c1-60db-4725-8e65-b33a95acb805'; + + try { + reservationsResolver.fetchReservationsByShop(invalidShopId); + } catch (error) { + expect(error).toBeInstanceOf(UnprocessableEntityException); + expect(error.message).toBe( + `가게가 ${invalidShopId}인 예약을 찾을 수 없습니다`, + ); + } + }); + + it('가게의 모든 예약 정보를 배열로 리턴해야 함', async () => { + mockReservationsRepository.find.mockImplementation( + (where: { shop: { id: string } }) => [MOCK_RESERVATION], + ); + mockReservationsService.findAllByShopId.mockImplementation( + (shopId: string) => [MOCK_RESERVATION], + ); + + const result = await reservationsResolver.fetchReservationsByShop( + MOCK_RESERVATION.shop.id, + ); + expect(result).toEqual([MOCK_RESERVATION]); + }); + }); + + // describe('findForShopDetailPage', () => { + // it('가게의 모든 리뷰 작성자 프로필과 리뷰를 배열로 리턴해야 함', () => { + // mockReservationsRepository.find.mockImplementation( + // (where: { shop: { id: string } }) => [MOCK_RESERVATION], + // ); + // mockReservationsRepository.find.mockImplementation( + // ( + // where: { shop: { id: string } }, // + // reservation: { id: string }, + // ) => [MOCK_REVIEW], + // ); + // }); + // }); + + describe('deleteReservation', () => { + it('예약을 찾을 수 없는 경우 UnprocessableEntityException를 던져야 함', () => { + mockReservationsRepository.findOne.mockImplementation( + (where: { id: string }) => null, + ); + const invalidReservationId = '6b7a693d-f0db-4e98-bddd-d855f5324ecf'; + + try { + reservationsResolver.deleteReservation(invalidReservationId); + } catch (error) { + expect(error).toBeInstanceOf(UnprocessableEntityException); + expect(error.message).toBe( + `예약ID가 ${invalidReservationId}인 예약을 찾을 수 없습니다`, + ); + } + }); + + it('삭제 여부를 true로 반환해야 함', async () => { + mockReservationsRepository.findOne.mockImplementation( + (where: { id: string }) => MOCK_RESERVATION, + ); + mockReservationsRepository.softDelete.mockImplementation( + (id: string) => 1, + ); + mockReservationsService.delete.mockImplementation( + (reservationId: string) => true, + ); + + const result = await reservationsResolver.deleteReservation( + MOCK_RESERVATION.id, + ); + expect(result).toBe(true); + }); }); });