From f14886a0d1e9f9100ddecba6bbe1c466ac436945 Mon Sep 17 00:00:00 2001 From: OptimistLabyrinth Date: Mon, 28 Nov 2022 19:41:33 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=AC=B8=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 마크다운 문법 사용해서 표(테이블)로 정리 --- README.md | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 192 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 7175c9e711..0895683b16 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,198 @@ ## 요구 사항 +## 상품 + +| 한글명 | 영문명 | 설명 | +|--------|---------|------------| +| 상품 | Product | 상품 도메인 엔티티 | +| 상품 아이디 | id | 상품의 고유 번호 | +| 상품 이름 | name | 상품의 이름 | +| 상품 가격 | price | 상품의 가격 | +| 상품 가격 | price | 상품의 가격 | + +### 상품 생성하기 (POST /api/products) + +- HTTP Response Code : 201 CREATED +- 가격 + - 가격은 반드시 있어야 한다 + - 가격은 0 이상의 정수여야 한다. + +### 상품 리스트 조회 (GET /api/products) + +- HTTP Response Code : 200 OK + +## 메뉴 그룹 + +| 한글명 | 영문명 | 설명 | +|----------|-----------|--------------| +| 메뉴 그룹 | MenuGroup | 여러 메뉴의 묶음 | +| 메뉴그룹 아이디 | id | 메뉴그룹 고유의 식별자 | +| 메뉴그룹 이름 | name | 메뉴 그룹의 이름 | + +### 메뉴 그룹 생성하기 (POST /api/menu-groups) + +- HTTP Response Code : 201 CREATED + +### 메뉴 그룹 리스트 조회하기 (GET /api/menu-groups) + +- HTTP Response Code : 200 OK + +## 메뉴 + +| 한글명 | 영문명 | 설명 | +|-------------|--------------|----------------------------| +| 메뉴 | Menu | 메뉴 | +| 메뉴 아이디 | id | 메뉴 고유 번호 | +| 메뉴 이름 | name | 메뉴의 이름 | +| 메뉴 가격 | price | 메뉴의 가격 | +| 속한 메뉴그룹 아이디 | menuGroupId | 해당 메뉴가 속한 메뉴그룹 아이디 | +| 메뉴의 상품 리스트 | menuProducts | 해당 메뉴를 만드는데 필요한 모든 상품의 리스트 | + +### 메뉴 생성하기 (POST /api/menus) + +- HTTP Response Code : 201 CREATED +- 가격 + - 가격은 반드시 있어야 한다 + - 가격은 0 이상의 정수여야 한다. +- 메뉴 그룹 + - 메뉴 그룹이 실제로 존재하지 않는다면 메뉴를 생성할 수 없다. +- 상품 리스트 + - 하나의 상품이라도 실제로 존재하지 않는다면 메뉴를 생성할 수 없다. +- 메뉴의 가격은 항상 모든 상품 가격의 총합보다 작거나 같아야 한다. + +### 메뉴 리스트 조회하기 (GET /api/menus) + +- HTTP Response Code : 200 OK + +## 메뉴 상품 + +| 한글명 | 영문명 | 설명 | +|--------|-------------|------------------------------| +| 메뉴 상품 | MenuProduct | 메뉴에 속한 상품 리스트에 대한 관계 도메인 엔티티 | +| 상품의 순서 | seq | 메뉴 내에서 상품이 위치한 순서 | +| 메뉴 아이디 | menuId | 상품이 포함된 메뉴의 아이디 | +| 상품 아이디 | productId | 상품의 고유번호 | +| 상품 수량 | quantity | 해당 메뉴에 포함된 상품의 개수 | + +## 테이블 그룹 + +| 한글명 | 영문명 | 설명 | +|-------------|-------------|---------------------------------| +| 테이블그룹 | TableGroup | 여러 테이블의 묶음. 여러 테이블에 걸쳐서 주문하는 경우 | +| 테이블 아이디 | id | 테이블 그룹 고유 식별자 | +| 테이블그룹 생성 시간 | createdDate | 테이블그룹을 생성한 시간 | +| 테이블 리스트 | orderTables | 해당 테이블 그룹에 속한 테이블 리스트 | + +### 테이블 그룹 생성하기 (POST /api/table-groups) + +- HTTP Response Code : 201 CREATED +- 테이블 리스트 + - 테이블 리스트가 반드시 있어야 한다. + - 테이블 리스트에 있는 테이블이 반드시 2개 이상이어야 한다. + - 모든 테이블 실제로 확인 + - 하나의 테이블이라도 실제로 존재하지 않는다면 테이블 그룹을 생성할 수 없다. + - 하나의 테이블이라도 빈 테이블이라면 테이블 그룹을 생성할 수 없다. + - 하나의 테이블이라도 다른 테이블 그룹에 속해있다면 테이블 그룹을 만들 수 없다. + +### 테이블 그룹 해제하기 (DELETE /api/table-groups/{tableGroupId}) + +- HTTP Response Code : 204 No Content +- 테이블 리스트 + - 하나의 테이블이라도 실제로 존재하지 않는다면 테이블 그룹을 해제할 수 없다. + - 테이블 상태가 '게산 완료' 라면 테이블 그룹을 해제할 수 없다. + +## 주문 + +| 한글명 | 영문명 | 설명 | +|---------------|----------------|------------------------------------------| +| 주문 | Order | 주문 정보 도메인 엔티티 | +| 주문 아이디 | id | 주문 고유 번호 | +| 주문 테이블 아이디 | orderTableId | 주문을 요청한 테이블 아이디 | +| 주문 상태 | orderStatus | COOKING, MEAL, COMPLETION 중에 하나의 상태를 가진다 | +| 주문 요청 시간 | orderedTime | 주문 요청이 이루어진 시간 | +| 주문 내 메뉴 수량 정보 | orderLineItems | 주문 내 메뉴 리스트. 개별 메뉴의 수량 정보를 포함한다 | + +## 주문 항목 + +| 한글명 | 영문명 | 설명 | +|--------|---------------|------------------------------| +| 주문 항목 | OrderLineItem | 주문에 포함된 메뉴의 상세 정보 관계 도메인 엔티티 | +| 메뉴의 순서 | seq | 주문 항목 내 메뉴의 순서 | +| 주문 아이디 | orderId | 해당 주문 항목이 속하는 주문의 고유 식별자 | +| 메뉴 아이디 | menuId | 해당 주문 항목이 가리키는 메뉴의 고유 식별자 | +| 주문 수량 | quantity | 주문한 메뉴의 수량 | + +### 주문 생성하기 (POST /api/orders) + +- HTTP Response Code : 201 CREATED +- 주문 항목 리스트 + - 주문 항목이 반드시 1 개 이상이어야 한다. + - 모든 메뉴 실제로 확인 + - 하나의 메뉴라도 실제로 존재하지 않는다면 주문을 생성할 수 없다. + - 주문 테이블이 실제로 존재하지 않는다면 주문을 생성할 수 없다. + +### 주문 리스트 조회하기 (GET /api/orders) + +- HTTP Response Code : 200 OK + +### 주문 상태 변경하기 (PUT /api/orders/{orderId}/order-status) + +- HTTP Response Code : 200 OK +- 주문 아이디 + - 존재하지 않는 주문에 대해서 상태 변경을 요청할 수 없다. +- 이전의 주문 상태 + - 이전의 주문 상태가 "계산 완료" 라면 상태 변경을 요청할 수 없다. + +## 주문 테이블 + +| 한글명 | 영문명 | 설명 | +|---------------|----------------|----------------------------------------------------------| +| 주문 테이블 | OrderTable | 주문은 기본적으로 테이블 단위로 요청한다. 테이블을 여러개 묶어서 테이블 그룹으로 관리할 수도 있다. | +| 주문 테이블 아이디 | id | 주문테이블 고유 번호 | +| 주문 테이블 그룹 아이디 | tableGroupId | 주문 테이블이 속한 테이블 그룹의 고유 식별자 | +| 손님 수 | numberOfGuests | 해당 테이블에 앉은 손님의 숫자 | +| 빈 테이블 여부 | empty | 해당 테이블에 손님이 있는지 없는지 확인하기 위한 플래그 변수 | + +## 테이블 생성 (POST /api/tables) + +- HTTP Response Code : 201 CREATED +- 새로운 테이블을 생성할 때 기본적으로 테이블 그룹 아이디는 null 로 초기화한다. + +### 테이블 리스트 조회 (GET /api/tables) + +- HTTP Response Code : 200 OK + +### 빈 테이블로 표시하기 (PUT /api/tables/{orderTableId}/empty) + +- HTTP Response Code : 200 OK +- 주문 테이블 아이디 + - 주문 테이블이 실제로 존재하지 않으면 빈 테이블로 표시할 수 없다. +- 주문 테이블이 테이블 그룹에 포함되어 있다면 빈 테이블로 표시할 수 없다. +- 주문 + - 주문 테이블에 해당하는 주문이 존재하지 않은 빈 테이블로 표시할 수 없다. + - 주문 테이블의 상태가 "계산 완료" 가 아니라면 빈 테이블로 표시할 수 없다. + +### 테이블의 손님 숫자 변경하기 (PUT /api/tables/{orderTableId}/number-of-guests) + +- HTTP Response Code : 200 OK +- 변경하려는 손님 숫자는 반드시 0 이상이어야 한다. +- 주문 테이블이 비어있으면 손님 숫자를 변경할 수 없다. + ## 용어 사전 -| 한글명 | 영문명 | 설명 | -| --- | --- | --- | -| 상품 | product | 메뉴를 관리하는 기준이 되는 데이터 | -| 메뉴 그룹 | menu group | 메뉴 묶음, 분류 | -| 메뉴 | menu | 메뉴 그룹에 속하는 실제 주문 가능 단위 | -| 메뉴 상품 | menu product | 메뉴에 속하는 수량이 있는 상품 | -| 금액 | amount | 가격 * 수량 | -| 주문 테이블 | order table | 매장에서 주문이 발생하는 영역 | -| 빈 테이블 | empty table | 주문을 등록할 수 없는 주문 테이블 | -| 주문 | order | 매장에서 발생하는 주문 | -| 주문 상태 | order status | 주문은 조리 ➜ 식사 ➜ 계산 완료 순서로 진행된다. | +| 한글명 | 영문명 | 설명 | +|----------|------------------|-------------------------------| +| 상품 | product | 메뉴를 관리하는 기준이 되는 데이터 | +| 메뉴 그룹 | menu group | 메뉴 묶음, 분류 | +| 메뉴 | menu | 메뉴 그룹에 속하는 실제 주문 가능 단위 | +| 메뉴 상품 | menu product | 메뉴에 속하는 수량이 있는 상품 | +| 금액 | amount | 가격 * 수량 | +| 주문 테이블 | order table | 매장에서 주문이 발생하는 영역 | +| 빈 테이블 | empty table | 주문을 등록할 수 없는 주문 테이블 | +| 주문 | order | 매장에서 발생하는 주문 | +| 주문 상태 | order status | 주문은 조리 ➜ 식사 ➜ 계산 완료 순서로 진행된다. | | 방문한 손님 수 | number of guests | 필수 사항은 아니며 주문은 0명으로 등록할 수 있다. | -| 단체 지정 | table group | 통합 계산을 위해 개별 주문 테이블을 그룹화하는 기능 | -| 주문 항목 | order line item | 주문에 속하는 수량이 있는 메뉴 | -| 매장 식사 | eat in | 포장하지 않고 매장에서 식사하는 것 | +| 단체 지정 | table group | 통합 계산을 위해 개별 주문 테이블을 그룹화하는 기능 | +| 주문 항목 | order line item | 주문에 속하는 수량이 있는 메뉴 | +| 매장 식사 | eat in | 포장하지 않고 매장에서 식사하는 것 | From d2529c2f7ccc925f9461d7e139c9240d2e6734f5 Mon Sep 17 00:00:00 2001 From: OptimistLabyrinth Date: Thu, 1 Dec 2022 20:21:22 +0900 Subject: [PATCH 02/10] =?UTF-8?q?docs:=20=EB=AC=B8=EC=84=9C=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=EC=9D=84=20=EA=B0=9C=EB=B0=9C=EC=9E=90=EA=B0=80=20?= =?UTF-8?q?=EC=95=84=EB=8B=8C=20=EA=B5=AC=EC=84=B1=EC=9B=90=EB=8F=84=20?= =?UTF-8?q?=EC=9D=B4=ED=95=B4=ED=95=98=EA=B8=B0=20=EC=89=AC=EC=9A=B4=20?= =?UTF-8?q?=EC=96=B8=EC=96=B4=EB=A1=9C=20=ED=91=9C=ED=98=84=ED=95=98?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 70 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 0895683b16..c7406b11ed 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,10 @@ | 상품 가격 | price | 상품의 가격 | | 상품 가격 | price | 상품의 가격 | +- 상품을 등록할 수 있다. +- 상품의 가격은 0원 이상이어야 한다. +- 상품의 목록을 조회할 수 있다. + ### 상품 생성하기 (POST /api/products) - HTTP Response Code : 201 CREATED @@ -31,6 +35,10 @@ | 메뉴그룹 아이디 | id | 메뉴그룹 고유의 식별자 | | 메뉴그룹 이름 | name | 메뉴 그룹의 이름 | +여러개의 메뉴를 묵음으로 관리할 수 있다. 이를 '메뉴 그룹' 이라고 한다. +메뉴 그룹을 생성할 수 있다. +메뉴 그룹의 목록을 조회할 수 있다. + ### 메뉴 그룹 생성하기 (POST /api/menu-groups) - HTTP Response Code : 201 CREATED @@ -50,17 +58,15 @@ | 속한 메뉴그룹 아이디 | menuGroupId | 해당 메뉴가 속한 메뉴그룹 아이디 | | 메뉴의 상품 리스트 | menuProducts | 해당 메뉴를 만드는데 필요한 모든 상품의 리스트 | +- 메뉴를 등록할 수 있다. +- 메뉴의 가격은 0원 이상이어야 한다. +- 하나의 메뉴에는 여러개의 상품을 포함할 수 있다. +- 메뉴의 가격은 포함하는 모든 상품의 총합을 초과할 수 없다. +- 메뉴를 등록할 때 메뉴 그룹을 지정할 수 있다. + ### 메뉴 생성하기 (POST /api/menus) - HTTP Response Code : 201 CREATED -- 가격 - - 가격은 반드시 있어야 한다 - - 가격은 0 이상의 정수여야 한다. -- 메뉴 그룹 - - 메뉴 그룹이 실제로 존재하지 않는다면 메뉴를 생성할 수 없다. -- 상품 리스트 - - 하나의 상품이라도 실제로 존재하지 않는다면 메뉴를 생성할 수 없다. -- 메뉴의 가격은 항상 모든 상품 가격의 총합보다 작거나 같아야 한다. ### 메뉴 리스트 조회하기 (GET /api/menus) @@ -85,23 +91,21 @@ | 테이블그룹 생성 시간 | createdDate | 테이블그룹을 생성한 시간 | | 테이블 리스트 | orderTables | 해당 테이블 그룹에 속한 테이블 리스트 | +- 여러개의 주문 테이블을 묶어서 단체 손님을 관리할 수 있다. +- 단체 손님 정보를 등록할 수 있다. +- 단체 손님으로 지정하려면 적어도 두 개 이상의 주문 테이블을 묶어야 한다. 한 개의 주문 테이블은 단체 손님으로 지정할 수 없다. +- 비어 있는 테이블은 단체 손님 정보에 포함할 수 없다. +- 하나의 주문 테이블을 동시에 두 개 이상의 단체 손님으로 지정할 수 없다. +- 단체 손님으로 지정했던 주문 테이블을 별도로 분리하는 것도 가능하다. +- 계산을 완료한 주문 테이블은 단체 손님에서 분리할 수 없다. + ### 테이블 그룹 생성하기 (POST /api/table-groups) - HTTP Response Code : 201 CREATED -- 테이블 리스트 - - 테이블 리스트가 반드시 있어야 한다. - - 테이블 리스트에 있는 테이블이 반드시 2개 이상이어야 한다. - - 모든 테이블 실제로 확인 - - 하나의 테이블이라도 실제로 존재하지 않는다면 테이블 그룹을 생성할 수 없다. - - 하나의 테이블이라도 빈 테이블이라면 테이블 그룹을 생성할 수 없다. - - 하나의 테이블이라도 다른 테이블 그룹에 속해있다면 테이블 그룹을 만들 수 없다. ### 테이블 그룹 해제하기 (DELETE /api/table-groups/{tableGroupId}) - HTTP Response Code : 204 No Content -- 테이블 리스트 - - 하나의 테이블이라도 실제로 존재하지 않는다면 테이블 그룹을 해제할 수 없다. - - 테이블 상태가 '게산 완료' 라면 테이블 그룹을 해제할 수 없다. ## 주문 @@ -124,14 +128,16 @@ | 메뉴 아이디 | menuId | 해당 주문 항목이 가리키는 메뉴의 고유 식별자 | | 주문 수량 | quantity | 주문한 메뉴의 수량 | +- 주문 요청이 들어오면 서버에 정보를 저장한다. +- 주문에는 여러개의 항목을 포함할 수 있다. +- 1개 이상의 항목을 선택해야만 주문을 요청할 수 있다. +- 주문 목록을 조회할 수도 있다. +- 외부 시스템에서 처리를 한 뒤에 주문 상태를 변경하는 것도 가능하다. +- 조리중 또는 식사중 상태인 주문만 상태를 변경 가능하다. 계산 완료한 뒤에는 더 이상 주문 상태를 변경할 수 없다. + ### 주문 생성하기 (POST /api/orders) - HTTP Response Code : 201 CREATED -- 주문 항목 리스트 - - 주문 항목이 반드시 1 개 이상이어야 한다. - - 모든 메뉴 실제로 확인 - - 하나의 메뉴라도 실제로 존재하지 않는다면 주문을 생성할 수 없다. - - 주문 테이블이 실제로 존재하지 않는다면 주문을 생성할 수 없다. ### 주문 리스트 조회하기 (GET /api/orders) @@ -140,10 +146,6 @@ ### 주문 상태 변경하기 (PUT /api/orders/{orderId}/order-status) - HTTP Response Code : 200 OK -- 주문 아이디 - - 존재하지 않는 주문에 대해서 상태 변경을 요청할 수 없다. -- 이전의 주문 상태 - - 이전의 주문 상태가 "계산 완료" 라면 상태 변경을 요청할 수 없다. ## 주문 테이블 @@ -155,10 +157,16 @@ | 손님 수 | numberOfGuests | 해당 테이블에 앉은 손님의 숫자 | | 빈 테이블 여부 | empty | 해당 테이블에 손님이 있는지 없는지 확인하기 위한 플래그 변수 | +- 주문 테이블 정보를 등록할 수 있다. +- 주문 테이블의 목록을 조회할 수 있다. +- 계산을 마친 주문 테이블은 비어있는 테이블로 표시할 수 있다. +- 단체 손님으로 지정한 주문 테이블은 빈 테이블로 표시할 수 없다. +- 조리중 또는 식사중 상태인 주문 테이블은 빈 테이블로 만들 수 없고, 계산 완료한 주문 테이블만 빈 테이블로 만들 수 있다. +- 테이블에 앉아 있는 손님의 숫자를 기록할 수 있다. + ## 테이블 생성 (POST /api/tables) - HTTP Response Code : 201 CREATED -- 새로운 테이블을 생성할 때 기본적으로 테이블 그룹 아이디는 null 로 초기화한다. ### 테이블 리스트 조회 (GET /api/tables) @@ -167,12 +175,6 @@ ### 빈 테이블로 표시하기 (PUT /api/tables/{orderTableId}/empty) - HTTP Response Code : 200 OK -- 주문 테이블 아이디 - - 주문 테이블이 실제로 존재하지 않으면 빈 테이블로 표시할 수 없다. -- 주문 테이블이 테이블 그룹에 포함되어 있다면 빈 테이블로 표시할 수 없다. -- 주문 - - 주문 테이블에 해당하는 주문이 존재하지 않은 빈 테이블로 표시할 수 없다. - - 주문 테이블의 상태가 "계산 완료" 가 아니라면 빈 테이블로 표시할 수 없다. ### 테이블의 손님 숫자 변경하기 (PUT /api/tables/{orderTableId}/number-of-guests) From 447ea40b2393b63e31d44421be9e6a7942511f88 Mon Sep 17 00:00:00 2001 From: OptimistLabyrinth Date: Sat, 3 Dec 2022 07:10:56 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EC=96=B4=EB=96=A4=20=EB=A8=B8?= =?UTF-8?q?=EC=8B=A0=EC=97=90=EC=84=9C=20=EC=8B=A4=ED=96=89=ED=95=98?= =?UTF-8?q?=EB=93=A0=20h2=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=B2=A0=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20app?= =?UTF-8?q?lication.prop=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 24ad19026a..90e636d545 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,5 @@ logging.level.org.springframework.jdbc.core=TRACE +spring.datasource.url=jdbc:h2:~/kitchenpos;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa spring.h2.console.enabled=true From cfb03ff29cd801616360e6aa5de57ea573a0a0e8 Mon Sep 17 00:00:00 2001 From: OptimistLabyrinth Date: Sat, 3 Dec 2022 09:41:14 +0900 Subject: [PATCH 04/10] =?UTF-8?q?test:=20ProductRestController,=20ProductS?= =?UTF-8?q?ervice=20=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=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 --- .../application/ProductServiceTest.java | 129 +++++++++++++++ .../ui/ProductRestControllerTest.java | 150 ++++++++++++++++++ 2 files changed, 279 insertions(+) create mode 100644 src/test/java/kitchenpos/application/ProductServiceTest.java create mode 100644 src/test/java/kitchenpos/ui/ProductRestControllerTest.java diff --git a/src/test/java/kitchenpos/application/ProductServiceTest.java b/src/test/java/kitchenpos/application/ProductServiceTest.java new file mode 100644 index 0000000000..aa1eb6e6fd --- /dev/null +++ b/src/test/java/kitchenpos/application/ProductServiceTest.java @@ -0,0 +1,129 @@ +package kitchenpos.application; + +import kitchenpos.dao.ProductDao; +import kitchenpos.domain.Product; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +@DisplayName("ProductService 클래스 테스트") +public class ProductServiceTest { + @Mock + private ProductDao productDao; + @InjectMocks + private ProductService productService; + + @Nested + @DisplayName("create 메서드 테스트") + public class CreateMethod { + @ParameterizedTest + @ValueSource(ints = {0, 1, 2, Integer.MAX_VALUE}) + @DisplayName("새로운 상품 생성 성공") + public void createSuccess(int priceValue) { + // given + final String name = "test product"; + final Product mockProduct = setupCreate(name, priceValue); + + // given + final Product product = new Product(); + product.setName(name); + product.setPrice(BigDecimal.valueOf(priceValue)); + + // when + final Product createdProduct = productService.create(product); + + // then + assertAll( + () -> assertThat(createdProduct.getName()).isEqualTo(mockProduct.getName()), + () -> assertThat(createdProduct.getPrice()).isEqualTo(mockProduct.getPrice()) + ); + } + + private Product setupCreate(String name, int price) { + final Product mockProduct = new Product(); + mockProduct.setName(name); + mockProduct.setPrice(BigDecimal.valueOf(price)); + Mockito.when(productDao.save(Mockito.any())).thenReturn(mockProduct); + return mockProduct; + } + + @ParameterizedTest + @NullSource + @DisplayName("새로운 상품 생성 성공") + public void createFailure_priceNull(BigDecimal bigDecimal) { + // given + final Product product = new Product(); + product.setPrice(bigDecimal); + + // when - then + assertThrows(IllegalArgumentException.class, () -> productService.create(product)); + } + + @ParameterizedTest + @ValueSource(ints = {Integer.MIN_VALUE, -2, -1}) + @DisplayName("새로운 상품 생성 성공") + public void createFailure_priceNegative(int priceValue) { + // given + final Product product = new Product(); + product.setPrice(BigDecimal.valueOf(priceValue)); + + // when - then + assertThrows(IllegalArgumentException.class, () -> productService.create(product)); + } + } + + + @Nested + @DisplayName("list 테스트") + public class ListMethod { + private void setup(List mockProducts) { + Mockito.when(productDao.findAll()).thenReturn(mockProducts); + } + + @Test + @DisplayName("리스트 조회 성공") + public void listSuccess() { + // given + final List mockProducts = Arrays.asList(new Product(), new Product(), new Product()); + setup(mockProducts); + + // when + final List products = productService.list(); + + // then + assertThat(products.size()).isEqualTo(mockProducts.size()); + } + + @Test + @DisplayName("리스트 조회 성공") + public void listSuccessEmptyList() { + // given + final List mockProducts = Collections.emptyList(); + setup(mockProducts); + + // when + final List products = productService.list(); + + // then + assertThat(products.size()).isEqualTo(0); + } + } +} diff --git a/src/test/java/kitchenpos/ui/ProductRestControllerTest.java b/src/test/java/kitchenpos/ui/ProductRestControllerTest.java new file mode 100644 index 0000000000..07d8bf277b --- /dev/null +++ b/src/test/java/kitchenpos/ui/ProductRestControllerTest.java @@ -0,0 +1,150 @@ +package kitchenpos.ui; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import kitchenpos.domain.Product; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; + +@WebMvcTest(ProductRestController.class) +@DisplayName("ProductRestController 테스트") +public class ProductRestControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private ProductRestController productRestController; + + @Nested + @DisplayName("POST /api/products 테스트") + public class PostMethod { + @Test + @DisplayName("성공적으로 상품을 등록하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + final Product mockProduct = setupSuccess("test product", 1234); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/products") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(mockProduct)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + + // then + final Product productResponse = + objectMapper.readValue(response.getContentAsString(), Product.class); + assertAll( + () -> assertThat(productResponse.getName()).isEqualTo(mockProduct.getName()), + () -> assertThat(productResponse.getPrice()).isEqualTo(mockProduct.getPrice()) + ); + } + + private Product setupSuccess(String name, int price) { + final Product mockProduct = new Product(); + mockProduct.setId((long) new Random().nextInt(Integer.MAX_VALUE)); + mockProduct.setName(name); + mockProduct.setPrice(BigDecimal.valueOf(price)); + Mockito.when(productRestController.create(Mockito.any())).thenReturn(ResponseEntity.ok(mockProduct)); + return mockProduct; + } + + @Test + @DisplayName("가격을 입력하지 않아서 상품 등록에 실패하면 400 상태 코드를 응답받는다") + public void errorPriceNull() throws Exception { + // given + final Product mockProduct = setupErrorPriceNullOrNegative("test product", BigDecimal.valueOf(1234)); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/products") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(mockProduct)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + private Product setupErrorPriceNullOrNegative(String name, BigDecimal price) { + final Product mockProduct = new Product(); + mockProduct.setId((long) new Random().nextInt(Integer.MAX_VALUE)); + mockProduct.setName(name); + mockProduct.setPrice(price); + Mockito.when(productRestController.create(Mockito.any())).thenReturn(ResponseEntity.badRequest().build()); + return mockProduct; + } + + @Test + @DisplayName("가격에 음수를 입력해서 상품 등록에 실패하면 400 상태 코드를 응답받는다") + public void errorPriceNegative() throws Exception { + // given + final Product mockProduct = setupErrorPriceNullOrNegative("test product", BigDecimal.valueOf(-999)); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/products") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(mockProduct)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("GET /api/products 테스트") + public class GetList { + @Test + @DisplayName("상품 목록을 조회하는 데 성공하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + final List mockProducts = setupSuccess(); + + // when + MockHttpServletResponse response = mockMvc.perform(get("/api/products") + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + + // then + final List productsResponse = + objectMapper.readValue(response.getContentAsString(), new TypeReference>() { + }); + assertThat(productsResponse.size()).isEqualTo(mockProducts.size()); + } + + private List setupSuccess() { + final List mockProducts = Arrays.asList(new Product(), new Product(), new Product()); + Mockito.when(productRestController.list()).thenReturn(ResponseEntity.ok(mockProducts)); + return mockProducts; + } + } +} From 8f103351d7fba0b046b3dda6fe6a754c23cb8035 Mon Sep 17 00:00:00 2001 From: OptimistLabyrinth Date: Sat, 3 Dec 2022 11:04:57 +0900 Subject: [PATCH 05/10] =?UTF-8?q?test:=20MenuGroupRestController,=20MenuGr?= =?UTF-8?q?oupService=20=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/MenuGroupServiceTest.java | 73 ++++++++++++++ .../ui/MenuGroupRestControllerTest.java | 98 +++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 src/test/java/kitchenpos/application/MenuGroupServiceTest.java create mode 100644 src/test/java/kitchenpos/ui/MenuGroupRestControllerTest.java diff --git a/src/test/java/kitchenpos/application/MenuGroupServiceTest.java b/src/test/java/kitchenpos/application/MenuGroupServiceTest.java new file mode 100644 index 0000000000..5a0b0e53c1 --- /dev/null +++ b/src/test/java/kitchenpos/application/MenuGroupServiceTest.java @@ -0,0 +1,73 @@ +package kitchenpos.application; + +import kitchenpos.dao.MenuGroupDao; +import kitchenpos.domain.MenuGroup; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +@DisplayName("MenuGroupService 클래스 테스트") +public class MenuGroupServiceTest { + @Mock + private MenuGroupDao menuGroupDao; + @InjectMocks + private MenuGroupService menuGroupService; + + @Nested + @DisplayName("create 메서드 테스트") + public class CreateMethod { + @Test + @DisplayName("메뉴그룹 생성 성공") + public void success() { + // given + final MenuGroup mockMenuGroup = setupSuccess("test menu group"); + + // when + final MenuGroup createdMenuGroup = menuGroupService.create(mockMenuGroup); + + // then + assertThat(createdMenuGroup.getName()).isEqualTo(mockMenuGroup.getName()); + } + + private MenuGroup setupSuccess(String name) { + final MenuGroup mockMenuGroup = new MenuGroup(); + mockMenuGroup.setName("test menu group"); + Mockito.when(menuGroupDao.save(Mockito.any())).thenReturn(mockMenuGroup); + return mockMenuGroup; + } + } + + @Nested + @DisplayName("list 메서드 테스트") + public class ListMethod { + @Test + @DisplayName("메뉴그룹 생성 성공") + public void success() { + // given + final List mockMenuGroups = setupSuccess(); + + // when + final List createdMenuGroups = menuGroupService.list(); + + // then + assertThat(createdMenuGroups.size()).isEqualTo(mockMenuGroups.size()); + } + + private List setupSuccess() { + final List mockMenuGroups = Arrays.asList(new MenuGroup(), new MenuGroup(), new MenuGroup()); + Mockito.when(menuGroupDao.findAll()).thenReturn(mockMenuGroups); + return mockMenuGroups; + } + } +} diff --git a/src/test/java/kitchenpos/ui/MenuGroupRestControllerTest.java b/src/test/java/kitchenpos/ui/MenuGroupRestControllerTest.java new file mode 100644 index 0000000000..ed649fb77a --- /dev/null +++ b/src/test/java/kitchenpos/ui/MenuGroupRestControllerTest.java @@ -0,0 +1,98 @@ +package kitchenpos.ui; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import kitchenpos.domain.MenuGroup; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; + +@WebMvcTest(MenuGroupRestController.class) +public class MenuGroupRestControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private MenuGroupRestController menuGroupRestController; + + @Nested + @DisplayName("POST /api/menu-groups 테스트") + public class PostMethod { + @Test + @DisplayName("성공적으로 메뉴그룹을 등록하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + final MenuGroup mockMenuGroup = setupSuccess("test menu group"); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/menu-groups") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(mockMenuGroup)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + + // then + final MenuGroup menuGroupResponse = + objectMapper.readValue(response.getContentAsString(), MenuGroup.class); + assertThat(menuGroupResponse.getName()).isEqualTo(mockMenuGroup.getName()); + } + + private MenuGroup setupSuccess(String name) { + final MenuGroup menuGroup = new MenuGroup(); + menuGroup.setName(name); + Mockito.when(menuGroupRestController.create(Mockito.any())).thenReturn(ResponseEntity.ok(menuGroup)); + return menuGroup; + } + } + + @Nested + @DisplayName("GET /api/menu-groups 테스트") + public class GetList { + @Test + @DisplayName("메뉴그룹 목록을 조회하는 데 성공하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + final List mockMenuGroups = setupSuccess(); + + // when + MockHttpServletResponse response = mockMvc.perform(get("/api/menu-groups") + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + + // then + final List menuGroupsResponse = + objectMapper.readValue(response.getContentAsString(), new TypeReference>() { + }); + assertThat(menuGroupsResponse.size()).isEqualTo(mockMenuGroups.size()); + } + + private List setupSuccess() { + final List mockMenuGroups = Arrays.asList(new MenuGroup(), new MenuGroup(), new MenuGroup()); + Mockito.when(menuGroupRestController.list()).thenReturn(ResponseEntity.ok(mockMenuGroups)); + return mockMenuGroups; + } + } +} From 1f495181f472f723f686ef22f0040835263af754 Mon Sep 17 00:00:00 2001 From: OptimistLabyrinth Date: Sat, 3 Dec 2022 14:27:57 +0900 Subject: [PATCH 06/10] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=84=A4?= =?UTF-8?q?=EB=AA=85(display=20name)=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EA=B3=A0=EC=B9=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/kitchenpos/application/ProductServiceTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/kitchenpos/application/ProductServiceTest.java b/src/test/java/kitchenpos/application/ProductServiceTest.java index aa1eb6e6fd..43f512ad37 100644 --- a/src/test/java/kitchenpos/application/ProductServiceTest.java +++ b/src/test/java/kitchenpos/application/ProductServiceTest.java @@ -67,7 +67,7 @@ private Product setupCreate(String name, int price) { @ParameterizedTest @NullSource - @DisplayName("새로운 상품 생성 성공") + @DisplayName("가격이 없으면 상품 등록 실패") public void createFailure_priceNull(BigDecimal bigDecimal) { // given final Product product = new Product(); @@ -79,7 +79,7 @@ public void createFailure_priceNull(BigDecimal bigDecimal) { @ParameterizedTest @ValueSource(ints = {Integer.MIN_VALUE, -2, -1}) - @DisplayName("새로운 상품 생성 성공") + @DisplayName("가격이 음수이면 상품 등록 실패") public void createFailure_priceNegative(int priceValue) { // given final Product product = new Product(); @@ -113,7 +113,7 @@ public void listSuccess() { } @Test - @DisplayName("리스트 조회 성공") + @DisplayName("빈 리스트 조회 성공") public void listSuccessEmptyList() { // given final List mockProducts = Collections.emptyList(); From 805ad805d4b8f881c793984b35b244424d16c394 Mon Sep 17 00:00:00 2001 From: OptimistLabyrinth Date: Sat, 3 Dec 2022 14:28:35 +0900 Subject: [PATCH 07/10] =?UTF-8?q?test:=20MenuRestController,=20MenuService?= =?UTF-8?q?=20=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=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 --- .../application/MenuServiceTest.java | 322 ++++++++++++++++++ .../kitchenpos/ui/MenuRestControllerTest.java | 168 +++++++++ 2 files changed, 490 insertions(+) create mode 100644 src/test/java/kitchenpos/application/MenuServiceTest.java create mode 100644 src/test/java/kitchenpos/ui/MenuRestControllerTest.java diff --git a/src/test/java/kitchenpos/application/MenuServiceTest.java b/src/test/java/kitchenpos/application/MenuServiceTest.java new file mode 100644 index 0000000000..d8cc8dfd6b --- /dev/null +++ b/src/test/java/kitchenpos/application/MenuServiceTest.java @@ -0,0 +1,322 @@ +package kitchenpos.application; + +import kitchenpos.dao.MenuDao; +import kitchenpos.dao.MenuGroupDao; +import kitchenpos.dao.MenuProductDao; +import kitchenpos.dao.ProductDao; +import kitchenpos.domain.Menu; +import kitchenpos.domain.MenuProduct; +import kitchenpos.domain.Product; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +@DisplayName("MenuService 클래스 테스트") +public class MenuServiceTest { + @Mock + private MenuDao menuDao; + @Mock + private MenuGroupDao menuGroupDao; + @Mock + private MenuProductDao menuProductDao; + @Mock + private ProductDao productDao; + @InjectMocks + private MenuService menuService; + + @Nested + @DisplayName("create 메서드 테스트") + public class CreateMethod { + @Nested + @DisplayName("메뉴 등록 성공") + public class Success { + @Test + public void testCase() { + // given + final Menu mockMenu = setup(); + + // when + final Menu createdMenu = menuService.create(mockMenu); + + // then + assertAll( + () -> assertThat(createdMenu.getName()).isEqualTo(mockMenu.getName()), + () -> assertThat(createdMenu.getPrice()).isEqualTo(mockMenu.getPrice()), + () -> assertThat(createdMenu.getMenuGroupId()).isEqualTo(mockMenu.getMenuGroupId()), + () -> assertAllMenuProducts(createdMenu, mockMenu) + ); + } + + private Menu setup() { + final long menuGroupId = 1L; + Mockito.when(menuGroupDao.existsById(menuGroupId)).thenReturn(true); + final List productsId = + Stream.of(11, 12, 13).map(Long::new).collect(Collectors.toList()); + final List products = productsId + .stream() + .map(productId -> { + final Product product = new Product(); + product.setId(productId); + product.setPrice(BigDecimal.valueOf(100 * productId)); + Mockito.when(productDao.findById(productId)).thenReturn(Optional.of(product)); + return product; + }) + .collect(Collectors.toList()); + final long menuId = 3L; + final List menuProducts = new ArrayList<>(); + for (int i = 0; i < products.size(); ++i) { + final Product product = products.get(i); + final MenuProduct menuProduct = new MenuProduct(); + menuProduct.setSeq((long) i + 1); + menuProduct.setMenuId(menuId); + menuProduct.setProductId(product.getId()); + menuProduct.setQuantity(product.getId() - 10); + menuProducts.add(menuProduct); + } + BigDecimal sumPrice = BigDecimal.ZERO; + for (Product product : products) { + final MenuProduct menuProduct = menuProducts + .stream() + .filter(x -> x.getProductId().equals(product.getId())) + .findFirst() + .get(); + sumPrice = sumPrice.add( + product.getPrice().multiply(BigDecimal.valueOf(menuProduct.getQuantity()))); + } + return mockMenu(menuId, "test menu", sumPrice, menuGroupId, menuProducts); + } + + private Menu mockMenu(long menuId, String name, BigDecimal price, long menuGroupId, + List menuProducts) { + final Menu menu = new Menu(); + menu.setId(menuId); + menu.setName(name); + menu.setPrice(price); + menu.setMenuGroupId(menuGroupId); + menu.setMenuProducts(menuProducts); + Mockito.when(menuDao.save(Mockito.any())).thenReturn(menu); + return menu; + } + + private void assertAllMenuProducts(Menu assertTarget, Menu assertOrigin) { + final List targetMenuProducts = assertTarget.getMenuProducts(); + final List originMenuProducts = assertOrigin.getMenuProducts(); + assertThat(targetMenuProducts.size()).isEqualTo(originMenuProducts.size()); + targetMenuProducts.forEach( + targetMenuProduct -> assertThat(originMenuProducts.contains(targetMenuProduct))); + } + } + + @Nested + @DisplayName("가격을 입력하지 않으면 메뉴 등록 실패") + public class ErrorPriceNull { + @Test + public void testCase() { + // given + final Menu menu = setup(); + + // when - then + assertThrows(IllegalArgumentException.class, () -> menuService.create(menu)); + } + + private Menu setup() { + final Menu menu = new Menu(); + menu.setPrice(null); + return menu; + } + } + + @Nested + @DisplayName("가격을 음수이면 메뉴 등록 실패") + public class ErrorPriceNegative { + @ParameterizedTest + @ValueSource(ints = {Integer.MIN_VALUE, -2, -1}) + public void testCase(int priceValue) { + // given + final Menu menu = setup(priceValue); + + // when - then + assertThrows(IllegalArgumentException.class, () -> menuService.create(menu)); + } + + private Menu setup(int priceValue) { + final Menu menu = new Menu(); + menu.setPrice(BigDecimal.valueOf(priceValue)); + return menu; + } + } + + @Nested + @DisplayName("지정한 메뉴 그룹이 없으면 메뉴 등록 실패") + public class ErrorMenuGroupDoesNotExist { + @Test + public void testCase() { + // given + final Menu menu = setup(); + + // when - then + assertThrows(IllegalArgumentException.class, () -> menuService.create(menu)); + } + + private Menu setup() { + final long menuGroupId = 1L; + Mockito.when(menuGroupDao.existsById(menuGroupId)).thenReturn(false); + final Menu menu = new Menu(); + menu.setPrice(BigDecimal.valueOf(1234)); + menu.setMenuGroupId(menuGroupId); + return menu; + } + } + + @Nested + @DisplayName("메뉴 가격이 포함된 상품의 총합보다 크면 메뉴 등록 실패") + public class ErrorMenuPriceExceedsSumOfProductPrice { + @Test + public void testCase() { + // given + final Menu menu = setup(); + + // when - then + assertThrows(IllegalArgumentException.class, () -> menuService.create(menu)); + } + + private Menu setup() { + final long menuGroupId = 1L; + Mockito.when(menuGroupDao.existsById(menuGroupId)).thenReturn(true); + final List productsId = + Stream.of(11, 12, 13).map(Long::new).collect(Collectors.toList()); + final List products = productsId + .stream() + .map(productId -> { + final Product product = new Product(); + product.setId(productId); + product.setPrice(BigDecimal.valueOf(100 * productId)); + Mockito.when(productDao.findById(productId)).thenReturn(Optional.of(product)); + return product; + }) + .collect(Collectors.toList()); + final long menuId = 3L; + final List menuProducts = new ArrayList<>(); + for (int i = 0; i < products.size(); ++i) { + final Product product = products.get(i); + final MenuProduct menuProduct = new MenuProduct(); + menuProduct.setSeq((long) i + 1); + menuProduct.setMenuId(menuId); + menuProduct.setProductId(product.getId()); + menuProduct.setQuantity(product.getId() - 10); + menuProducts.add(menuProduct); + } + BigDecimal sumPrice = BigDecimal.ZERO; + for (Product product : products) { + final MenuProduct menuProduct = menuProducts + .stream() + .filter(x -> x.getProductId().equals(product.getId())) + .findFirst() + .get(); + sumPrice = sumPrice.add( + product.getPrice().multiply(BigDecimal.valueOf(menuProduct.getQuantity()))); + } + final BigDecimal price = sumPrice.add(BigDecimal.valueOf(1000)); + return mockMenu(menuId, "test menu", price, menuGroupId, menuProducts + ); + } + + private Menu mockMenu(long menuId, String name, BigDecimal price, long menuGroupId, + List menuProducts) { + final Menu menu = new Menu(); + menu.setId(menuId); + menu.setName(name); + menu.setPrice(price); + menu.setMenuGroupId(menuGroupId); + menu.setMenuProducts(menuProducts); + return menu; + } + } + } + + @Nested + @DisplayName("list 메서드 테스트") + public class ListMethod { + @Nested + @DisplayName("메뉴 목록 조회 성공") + public class Success { + @Test + public void testCase() { + // given + final List menuIds = Stream.of(1, 2, 3).map(Long::new).collect(Collectors.toList()); + final List productIds = Stream.of(11, 12, 13).map(Long::new).collect(Collectors.toList()); + setup(menuIds, productIds); + + // when + final List menus = menuService.list(); + + // then + assertAll( + () -> assertThat(menus.size()).isEqualTo(menuIds.size()), + () -> assertAllMenuItem(menus, menuIds) + ); + } + + private void setup(List menuIds, List productIds) { + final List menus = menuIds.stream().map(this::newMenu).collect(Collectors.toList()); + Mockito.when(menuDao.findAll()).thenReturn(menus); + final List products = productIds.stream().map(this::newProduct).collect(Collectors.toList()); + final List menuProducts = menus + .stream() + .map(menu -> + products + .stream() + .map(product -> { + final MenuProduct menuProduct = new MenuProduct(); + menuProduct.setMenuId(menu.getId()); + menuProduct.setProductId(product.getId()); + return menuProduct; + }) + .collect(Collectors.toList()) + ) + .flatMap(List::stream) + .collect(Collectors.toList()); + Mockito + .when(menuProductDao.findAllByMenuId(1L)) + .thenReturn(menuProducts + .stream() + .filter(x -> x.getMenuId().equals(1L)) + .collect(Collectors.toList()) + ); + } + + private Menu newMenu(long menuId) { + final Menu menu = new Menu(); + menu.setId(menuId); + return menu; + } + + private Product newProduct(long productId) { + final Product product = new Product(); + product.setId(productId); + return product; + } + + private void assertAllMenuItem(List menus, List menuIds) { + menus.stream().map(Menu::getId).collect(Collectors.toList()).forEach(menuIds::contains); + } + } + } +} diff --git a/src/test/java/kitchenpos/ui/MenuRestControllerTest.java b/src/test/java/kitchenpos/ui/MenuRestControllerTest.java new file mode 100644 index 0000000000..18423be6e3 --- /dev/null +++ b/src/test/java/kitchenpos/ui/MenuRestControllerTest.java @@ -0,0 +1,168 @@ +package kitchenpos.ui; + +import com.fasterxml.jackson.databind.ObjectMapper; +import kitchenpos.domain.Menu; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; + +@WebMvcTest(MenuRestController.class) +@DisplayName("MenuRestController 클래스 테스트") +public class MenuRestControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private MenuRestController menuRestController; + + @Nested + @DisplayName("POST /api/menus 테스트") + public class PostMethod { + @Test + @DisplayName("성공적으로 메류를 등록하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + final Menu menu = setup(); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/menus") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(menu)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + + // then + final Menu menuResponse = + objectMapper.readValue(response.getContentAsString(), Menu.class); + assertThat(menuResponse.getName()).isEqualTo(menu.getName()); + } + + private Menu setup() { + final Menu menu = new Menu(); + menu.setName("test menu"); + menu.setPrice(BigDecimal.valueOf(0)); + Mockito.when(menuRestController.create(Mockito.any())).thenReturn(ResponseEntity.ok(menu)); + return menu; + } + + @Test + @DisplayName("가격을 입력하지 않아서 메류를 등록하지 못하면 400 상태 코드를 응답받는다") + public void badRequestPriceNull() throws Exception { + // given + final Menu menu = setupBadRequestPriceNull(); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/menus") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(menu)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + private Menu setupBadRequestPriceNull() { + final Menu menu = new Menu(); + menu.setName("test menu"); + menu.setPrice(null); + Mockito.when(menuRestController.create(Mockito.any())).thenReturn(ResponseEntity.badRequest().build()); + return menu; + } + + @Test + @DisplayName("가격을 음수로 입력해서 메류를 등록하지 못하면 400 상태 코드를 응답받는다") + public void badRequestPriceNegative() throws Exception { + // given + final Menu menu = setupBadRequestPriceNegative(); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/menus") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(menu)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + private Menu setupBadRequestPriceNegative() { + final Menu menu = new Menu(); + menu.setName("test menu"); + menu.setPrice(BigDecimal.valueOf(-999)); + Mockito.when(menuRestController.create(Mockito.any())).thenReturn(ResponseEntity.badRequest().build()); + return menu; + } + + @Test + @DisplayName("지정한 메뉴 가격이 포함하고 있는 상품의 가격 총합보다 커서 메류를 등록하지 못하면 400 상태 코드를 응답받는다") + public void badRequestPriceMoreThanSumOfProductPrice() throws Exception { + // given + final Menu menu = setupBadRequestPriceMoreThanSumOfProductPrice(); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/menus") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(menu)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + private Menu setupBadRequestPriceMoreThanSumOfProductPrice() { + final Menu menu = new Menu(); + menu.setName("test menu"); + menu.setPrice(BigDecimal.valueOf(1500)); + Mockito.when(menuRestController.create(Mockito.any())).thenReturn(ResponseEntity.badRequest().build()); + return menu; + } + } + + @Nested + @DisplayName("GET /api/menus 테스트") + public class GetList { + @Test + @DisplayName("성공적으로 메뉴 목록을 조회하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + setup(); + + // when + MockHttpServletResponse response = mockMvc.perform(get("/api/menus") + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + } + + private void setup() { + List menus = Arrays.asList(new Menu(), new Menu(), new Menu()); + Mockito.when(menuRestController.list()).thenReturn(ResponseEntity.ok(menus)); + } + } +} From 23d9850510c12ef44200fa288d8f727d59ddbcb6 Mon Sep 17 00:00:00 2001 From: OptimistLabyrinth Date: Mon, 12 Dec 2022 22:14:02 +0900 Subject: [PATCH 08/10] =?UTF-8?q?test:=20TableGroupRestController,=20Table?= =?UTF-8?q?GroupService=20=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=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 --- README.md | 2 +- .../application/TableGroupServiceTest.java | 227 ++++++++++++++++++ .../ui/TableGroupRestControllerTest.java | 150 ++++++++++++ 3 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 src/test/java/kitchenpos/application/TableGroupServiceTest.java create mode 100644 src/test/java/kitchenpos/ui/TableGroupRestControllerTest.java diff --git a/README.md b/README.md index c7406b11ed..1047e0422e 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ - 비어 있는 테이블은 단체 손님 정보에 포함할 수 없다. - 하나의 주문 테이블을 동시에 두 개 이상의 단체 손님으로 지정할 수 없다. - 단체 손님으로 지정했던 주문 테이블을 별도로 분리하는 것도 가능하다. -- 계산을 완료한 주문 테이블은 단체 손님에서 분리할 수 없다. +- 계산을 완료한 주문 테이블만 단체 손님에서 분리할 수 없다. ### 테이블 그룹 생성하기 (POST /api/table-groups) diff --git a/src/test/java/kitchenpos/application/TableGroupServiceTest.java b/src/test/java/kitchenpos/application/TableGroupServiceTest.java new file mode 100644 index 0000000000..afe0218e30 --- /dev/null +++ b/src/test/java/kitchenpos/application/TableGroupServiceTest.java @@ -0,0 +1,227 @@ +package kitchenpos.application; + +import kitchenpos.dao.OrderDao; +import kitchenpos.dao.OrderTableDao; +import kitchenpos.dao.TableGroupDao; +import kitchenpos.domain.OrderTable; +import kitchenpos.domain.TableGroup; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("TableGroupService 클래스 테스트") +public class TableGroupServiceTest { + @Mock + private OrderDao orderDao; + @Mock + private OrderTableDao orderTableDao; + @Mock + private TableGroupDao tableGroupDao; + @InjectMocks + private TableGroupService tableGroupService; + + @Nested + @DisplayName("create 메서드 테스트") + public class CreateMethod { + @Nested + @DisplayName("테이블 그룹 생성 성공") + public class Success { + @Test + public void testCase() { + // given + final TableGroup tableGroup = setup(); + + // when + final TableGroup createdTableGroup = tableGroupService.create(tableGroup); + + // then + assertAll( + () -> assertThat(createdTableGroup.getId()).isPositive(), + () -> assertThat(createdTableGroup.getOrderTables()).isEqualTo(tableGroup.getOrderTables()) + ); + } + + private TableGroup setup() { + final TableGroup tableGroup = new TableGroup(); + tableGroup.setId(1L); + final List orderTableIds = Stream.of(1, 2).map(Long::new).collect(Collectors.toList()); + final List orderTables = orderTableIds + .stream() + .map(id -> { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(id); + orderTable.setEmpty(true); + return orderTable; + }) + .collect(Collectors.toList()); + tableGroup.setOrderTables(orderTables); + Mockito.when(orderTableDao.findAllByIdIn(Mockito.anyList())).thenReturn(orderTables); + Mockito.when(tableGroupDao.save(Mockito.any())).thenReturn(tableGroup); + Mockito.when(orderTableDao.save(Mockito.any())).thenReturn(orderTables.get(0), orderTables.get(1)); + return tableGroup; + } + } + + @Nested + @DisplayName("주문 테이블이 하나도 없으면 테이블 그룹 생성 실패") + public class ErrorOrderTableIsEmpty { + @Test + public void testCase() { + // given + final TableGroup tableGroup = setup(); + + // when - then + assertThrows(IllegalArgumentException.class, () -> tableGroupService.create(tableGroup)); + } + + private TableGroup setup() { + final TableGroup tableGroup = new TableGroup(); + tableGroup.setOrderTables(new ArrayList<>()); + return tableGroup; + } + } + + @Nested + @DisplayName("주문 테이블이 한 개 뿐이면 테이블 그룹 생성 실패") + public class ErrorOnlyOneInOrderTables { + @Test + public void testCase() { + // given + final TableGroup tableGroup = setup(); + + // when - then + assertThrows(IllegalArgumentException.class, () -> tableGroupService.create(tableGroup)); + } + + private TableGroup setup() { + final TableGroup tableGroup = new TableGroup(); + final List orderTables = Arrays.asList(new OrderTable()); + tableGroup.setOrderTables(orderTables); + return tableGroup; + } + } + + @Nested + @DisplayName("주문 테이블 id 로 실제 주문 테이블을 하나라도 찾을 수 없으면 테이블 그룹 생성 실패") + public class ErrorsOrderTablesMissingWhenTryToFindById { + @Test + public void testCase() { + // given + final TableGroup tableGroup = setup(); + + // when - then + assertThrows(IllegalArgumentException.class, () -> tableGroupService.create(tableGroup)); + } + + private TableGroup setup() { + final TableGroup tableGroup = new TableGroup(); + tableGroup.setId(1L); + final List orderTableIds = Stream.of(1, 2).map(Long::new).collect(Collectors.toList()); + final List orderTables = orderTableIds + .stream() + .map(id -> { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(id); + orderTable.setEmpty(true); + return orderTable; + }) + .collect(Collectors.toList()); + tableGroup.setOrderTables(orderTables); + Mockito.when(orderTableDao.findAllByIdIn(Mockito.anyList())).thenReturn(orderTables.subList(0, 1)); + return tableGroup; + } + } + } + + @Nested + @DisplayName("ungroup 메서드 테스트") + public class UngroupMethod { + @Nested + @DisplayName("그룹 해제 성공") + public class Success { + @Test + public void testCase() { + // given + final TableGroup tableGroup = setup(); + final TableGroup createdTableGroup = tableGroupService.create(tableGroup); + + // when - then + assertDoesNotThrow(() -> tableGroupService.ungroup(createdTableGroup.getId())); + } + + private TableGroup setup() { + final TableGroup tableGroup = new TableGroup(); + tableGroup.setId(1L); + final List orderTableIds = Stream.of(1, 2).map(Long::new).collect(Collectors.toList()); + final List orderTables = orderTableIds + .stream() + .map(id -> { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(id); + orderTable.setEmpty(true); + return orderTable; + }) + .collect(Collectors.toList()); + tableGroup.setOrderTables(orderTables); + Mockito.when(orderTableDao.findAllByIdIn(Mockito.anyList())).thenReturn(orderTables); + Mockito.when(tableGroupDao.save(Mockito.any())).thenReturn(tableGroup); + Mockito.when(orderTableDao.save(Mockito.any())).thenReturn(orderTables.get(0), orderTables.get(1)); + Mockito.when(orderDao.existsByOrderTableIdInAndOrderStatusIn(Mockito.anyList(), Mockito.anyList())) + .thenReturn(false); + return tableGroup; + } + } + + @Nested + @DisplayName("그룹 해제 성공") + public class ErrorOrderStillCookingOrMeal { + @Test + public void testCase() { + // given + final TableGroup tableGroup = setup(); + final TableGroup createdTableGroup = tableGroupService.create(tableGroup); + + // when - then + assertThrows(IllegalArgumentException.class, + () -> tableGroupService.ungroup(createdTableGroup.getId())); + } + + private TableGroup setup() { + final TableGroup tableGroup = new TableGroup(); + tableGroup.setId(1L); + final List orderTableIds = Stream.of(1, 2).map(Long::new).collect(Collectors.toList()); + final List orderTables = orderTableIds + .stream() + .map(id -> { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(id); + orderTable.setEmpty(true); + return orderTable; + }) + .collect(Collectors.toList()); + tableGroup.setOrderTables(orderTables); + Mockito.when(orderTableDao.findAllByIdIn(Mockito.anyList())).thenReturn(orderTables); + Mockito.when(tableGroupDao.save(Mockito.any())).thenReturn(tableGroup); + Mockito.when(orderTableDao.save(Mockito.any())).thenReturn(orderTables.get(0), orderTables.get(1)); + Mockito.when(orderDao.existsByOrderTableIdInAndOrderStatusIn(Mockito.anyList(), Mockito.anyList())) + .thenReturn(true); + return tableGroup; + } + } + } +} diff --git a/src/test/java/kitchenpos/ui/TableGroupRestControllerTest.java b/src/test/java/kitchenpos/ui/TableGroupRestControllerTest.java new file mode 100644 index 0000000000..028c0bae63 --- /dev/null +++ b/src/test/java/kitchenpos/ui/TableGroupRestControllerTest.java @@ -0,0 +1,150 @@ +package kitchenpos.ui; + +import com.fasterxml.jackson.databind.ObjectMapper; +import kitchenpos.domain.OrderTable; +import kitchenpos.domain.TableGroup; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; + +@WebMvcTest(TableGroupRestController.class) +@DisplayName("TableGroupRestController 클래스 테스트") +public class TableGroupRestControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private TableGroupRestController tableGroupRestController; + + @Nested + @DisplayName("POST /api/table-groups") + public class PostMethod { + @Test + @DisplayName("성공적으로 테이블 그룹을 생성하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + final TableGroup tableGroup = setupSuccess(); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/table-groups") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(tableGroup)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + + // then + final TableGroup tableGroupResponse = + objectMapper.readValue(response.getContentAsString(), TableGroup.class); + assertThat(tableGroupResponse.getOrderTables()).hasSize(tableGroup.getOrderTables().size()); + } + + private TableGroup setupSuccess() { + final TableGroup tableGroup = new TableGroup(); + final List orderTables = Arrays.asList(new OrderTable(), new OrderTable()); + tableGroup.setOrderTables(orderTables); + Mockito.when(tableGroupRestController.create(Mockito.any())).thenReturn(ResponseEntity.ok(tableGroup)); + return tableGroup; + } + + @Test + @DisplayName("테이블 그룹을 생성하는데 실패하면 400 상태 코드를 응답받는다") + public void errorBadRequest() throws Exception { + // given + final TableGroup tableGroup = setupErrorBadRequest(); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/table-groups") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(tableGroup)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + private TableGroup setupErrorBadRequest() { + final TableGroup tableGroup = new TableGroup(); + tableGroup.setOrderTables(new ArrayList<>()); + Mockito.when(tableGroupRestController.create(Mockito.any())).thenReturn(ResponseEntity.badRequest().build()); + return tableGroup; + } + } + + @Nested + @DisplayName("DELETE /api/table-groups/{tableGroupId}") + public class DeleteMethod { + @Test + @DisplayName("성공적으로 테이블 그룹을 해제하면 204 상태 코드를 응답받는다") + public void success() throws Exception { + // given + final TableGroup tableGroup = setupSuccess(); + + // when + MockHttpServletResponse response = mockMvc + .perform(delete("/api/table-groups/" + tableGroup.getId()) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value()); + } + + private TableGroup setupSuccess() { + final TableGroup tableGroup = new TableGroup(); + tableGroup.setId(1L); + final List orderTables = Arrays.asList(new OrderTable(), new OrderTable()); + tableGroup.setOrderTables(orderTables); + Mockito.when(tableGroupRestController.ungroup(Mockito.anyLong())) + .thenReturn(ResponseEntity.noContent().build()); + return tableGroup; + } + + @Test + @DisplayName("테이블 그룹을 해제하는데 실패하면 400 상태 코드를 응답받는다") + public void errorBadRequest() throws Exception { + // given + final TableGroup tableGroup = setupErrorBadRequest(); + + // when + MockHttpServletResponse response = mockMvc + .perform(delete("/api/table-groups/" + tableGroup.getId()) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + private TableGroup setupErrorBadRequest() { + final TableGroup tableGroup = new TableGroup(); + tableGroup.setId(1L); + final List orderTables = Arrays.asList(new OrderTable(), new OrderTable()); + tableGroup.setOrderTables(orderTables); + Mockito.when(tableGroupRestController.ungroup(Mockito.anyLong())) + .thenReturn(ResponseEntity.badRequest().build()); + return tableGroup; + } + } +} From a1bf6340da164950a1a4c8f9b8f0a458a3446b68 Mon Sep 17 00:00:00 2001 From: OptimistLabyrinth Date: Tue, 13 Dec 2022 00:30:48 +0900 Subject: [PATCH 09/10] =?UTF-8?q?test:=20OrderRestController,=20OrderServi?= =?UTF-8?q?ce=20=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=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 --- .../application/OrderServiceTest.java | 334 ++++++++++++++++++ .../ui/OrderRestControllerTest.java | 189 ++++++++++ 2 files changed, 523 insertions(+) create mode 100644 src/test/java/kitchenpos/application/OrderServiceTest.java create mode 100644 src/test/java/kitchenpos/ui/OrderRestControllerTest.java diff --git a/src/test/java/kitchenpos/application/OrderServiceTest.java b/src/test/java/kitchenpos/application/OrderServiceTest.java new file mode 100644 index 0000000000..15468d5a25 --- /dev/null +++ b/src/test/java/kitchenpos/application/OrderServiceTest.java @@ -0,0 +1,334 @@ +package kitchenpos.application; + +import kitchenpos.dao.MenuDao; +import kitchenpos.dao.OrderDao; +import kitchenpos.dao.OrderLineItemDao; +import kitchenpos.dao.OrderTableDao; +import kitchenpos.domain.Order; +import kitchenpos.domain.OrderLineItem; +import kitchenpos.domain.OrderStatus; +import kitchenpos.domain.OrderTable; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +@DisplayName("OrderService 클래스 테스트") +public class OrderServiceTest { + @Mock + private MenuDao menuDao; + @Mock + private OrderDao orderDao; + @Mock + private OrderLineItemDao orderLineItemDao; + @Mock + private OrderTableDao orderTableDao; + @InjectMocks + private OrderService orderService; + + @Nested + @DisplayName("create 메서드 테스트") + public class CreateMethod { + @Nested + @DisplayName("주문 생성 성공") + public class Success { + @Test + public void testCase() { + // given + final Order order = setup(); + + // when + final Order createdOrder = orderService.create(order); + + // then + assertAll( + () -> assertThat(createdOrder.getId()).isPositive(), + () -> assertThat(createdOrder.getOrderStatus()).isEqualTo(OrderStatus.COOKING.name()), + () -> assertThat(createdOrder.getOrderLineItems()).hasSize(order.getOrderLineItems().size()) + ); + } + + private Order setup() { + final Order order = new Order(); + order.setId(1L); + final List orderLineItemIds = Stream.of(1, 2, 3).map(Long::new).collect(Collectors.toList()); + final List menuIds = orderLineItemIds.stream().map(id -> id * 2).collect(Collectors.toList()); + final List orderLineItems = orderLineItemIds.stream() + .map(id -> { + final OrderLineItem orderLineItem = new OrderLineItem(); + orderLineItem.setSeq(id - 1); + orderLineItem.setOrderId(order.getId()); + orderLineItem.setMenuId(menuIds.get((int) (id - 1))); + return orderLineItem; + }) + .collect(Collectors.toList()); + order.setOrderLineItems(orderLineItems); + Mockito.when(menuDao.countByIdIn(Mockito.anyList())).thenReturn((long) orderLineItems.size()); + final OrderTable orderTable = new OrderTable(); + orderTable.setId(11L); + orderTable.setEmpty(false); + order.setOrderTableId(orderTable.getId()); + Mockito.when(orderTableDao.findById(Mockito.anyLong())).thenReturn(Optional.of(orderTable)); + Mockito.when(orderDao.save(Mockito.any())).thenReturn(order); + Mockito.when(orderLineItemDao.save(Mockito.any())) + .thenReturn(orderLineItems.get(0), orderLineItems.get(1), orderLineItems.get(2)); + return order; + } + } + + @Nested + @DisplayName("주문 아이템 리스트가 비어있다면 주문 생성 실패") + public class ErrorOrderLineItemsEmpty { + @Test + public void testCase() { + // given + final Order order = setup(); + + // when - then + assertThrows(IllegalArgumentException.class, () -> orderService.create(order)); + } + + private Order setup() { + final Order order = new Order(); + order.setId(1L); + final List orderLineItemIds = Stream.of(1, 2, 3).map(Long::new).collect(Collectors.toList()); + final List menuIds = orderLineItemIds.stream().map(id -> id * 2).collect(Collectors.toList()); + final List orderLineItems = orderLineItemIds.stream() + .map(id -> { + final OrderLineItem orderLineItem = new OrderLineItem(); + orderLineItem.setSeq(id - 1); + orderLineItem.setOrderId(order.getId()); + orderLineItem.setMenuId(menuIds.get((int) (id - 1))); + return orderLineItem; + }) + .collect(Collectors.toList()); + order.setOrderLineItems(orderLineItems); + Mockito.when(menuDao.countByIdIn(Mockito.anyList())).thenReturn((long) orderLineItems.size()); + return order; + } + } + + @Nested + @DisplayName("주문 아이템 개수와 실제로 존재하는 메뉴 개수가 일치하지 않는다면 주문 생성 실패") + public class ErrorOrderLineItemsCountAndExistingMenuCountNotEqual { + @Test + public void testCase() { + // given + final Order order = setup(); + + // when - then + assertThrows(IllegalArgumentException.class, () -> orderService.create(order)); + } + + private Order setup() { + final Order order = new Order(); + order.setId(1L); + final List orderLineItemIds = Stream.of(1, 2, 3).map(Long::new).collect(Collectors.toList()); + final List menuIds = orderLineItemIds.stream().map(id -> id * 2).collect(Collectors.toList()); + final List orderLineItems = orderLineItemIds.stream() + .map(id -> { + final OrderLineItem orderLineItem = new OrderLineItem(); + orderLineItem.setSeq(id - 1); + orderLineItem.setOrderId(order.getId()); + orderLineItem.setMenuId(menuIds.get((int) (id - 1))); + return orderLineItem; + }) + .collect(Collectors.toList()); + order.setOrderLineItems(orderLineItems); + Mockito.when(menuDao.countByIdIn(Mockito.anyList())).thenReturn((long) orderLineItems.size() - 1); + return order; + } + } + + @Nested + @DisplayName("주문 테이블이 비어있다면 주문 생성 실패") + public class ErrorOrderTableIsEmpty { + @Test + public void testCase() { + // given + final Order order = setup(); + + // when - then + assertThrows(IllegalArgumentException.class, () -> orderService.create(order)); + } + + private Order setup() { + final Order order = new Order(); + order.setId(1L); + final List orderLineItemIds = Stream.of(1, 2, 3).map(Long::new).collect(Collectors.toList()); + final List menuIds = orderLineItemIds.stream().map(id -> id * 2).collect(Collectors.toList()); + final List orderLineItems = orderLineItemIds.stream() + .map(id -> { + final OrderLineItem orderLineItem = new OrderLineItem(); + orderLineItem.setSeq(id - 1); + orderLineItem.setOrderId(order.getId()); + orderLineItem.setMenuId(menuIds.get((int) (id - 1))); + return orderLineItem; + }) + .collect(Collectors.toList()); + order.setOrderLineItems(orderLineItems); + Mockito.when(menuDao.countByIdIn(Mockito.anyList())).thenReturn((long) orderLineItems.size()); + final OrderTable orderTable = new OrderTable(); + orderTable.setId(11L); + orderTable.setEmpty(true); + order.setOrderTableId(orderTable.getId()); + Mockito.when(orderTableDao.findById(Mockito.anyLong())).thenReturn(Optional.of(orderTable)); + return order; + } + } + } + + @Nested + @DisplayName("list 메서드 테스트") + public class ListMethod { + @Nested + @DisplayName("주문 목록 조회 성공") + public class Success { + @Test + public void testCase() { + // given + final List orders = setup(); + + // when + final List gottenOrders = orderService.list(); + + // then + assertThat(gottenOrders).hasSize(orders.size()); + } + + private List setup() { + final List orderIds = Stream.of(1, 2, 3).map(Long::new).collect(Collectors.toList()); + final List orders = orderIds.stream() + .map(id -> { + final Order order = new Order(); + order.setId(id); + return order; + }) + .collect(Collectors.toList()); + Mockito.when(orderService.list()).thenReturn(orders); + return orders; + } + } + + @Nested + @DisplayName("주문이 0 개인 경우에도 목록 조회 성공") + public class SuccessEmptyList { + @Test + public void testCase() { + // given + setup(); + + // when + final List gottenOrders = orderService.list(); + + // then + assertThat(gottenOrders).hasSize(0); + } + + private void setup() { + final List orders = new ArrayList<>(); + Mockito.when(orderService.list()).thenReturn(orders); + } + } + } + + @Nested + @DisplayName("changeOrderStatus 메서드 테스트") + public class ChangeOrderStatusMethod { + @Nested + @DisplayName("주문 상태 변경 성공") + public class Success { + @Test + public void testCase() { + // given + final Order order = setup(); + final Order newOrder = new Order(); + newOrder.setOrderStatus(OrderStatus.MEAL.name()); + + // when + final Order updatedOrder = orderService.changeOrderStatus(order.getId(), newOrder); + + // then + assertAll( + () -> assertThat(updatedOrder.getId()).isEqualTo(order.getId()), + () -> assertThat(updatedOrder.getOrderStatus()).isEqualTo(newOrder.getOrderStatus()) + ); + } + + private Order setup() { + final Order orderBefore = new Order(); + orderBefore.setId(1L); + orderBefore.setOrderStatus(OrderStatus.COOKING.name()); + Mockito.when(orderDao.findById(Mockito.anyLong())).thenReturn(Optional.of(orderBefore)); + final Order orderAfter = new Order(); + orderAfter.setId(1L); + orderAfter.setOrderStatus(OrderStatus.MEAL.name()); + Mockito.when(orderDao.save(Mockito.any())).thenReturn(orderAfter); + return orderBefore; + } + } + + @Nested + @DisplayName("해당 주문 id 로 주문을 찾지 못하는 경우 주문 상태 변경 실패") + public class ErrorOrderNotFound { + @Test + public void testCase() { + // given + final Order order = setup(); + final Order newOrder = new Order(); + newOrder.setOrderStatus(OrderStatus.MEAL.name()); + + // when - then + assertThrows(IllegalArgumentException.class, + () -> orderService.changeOrderStatus(order.getId(), newOrder)); + } + + private Order setup() { + final Order orderBefore = new Order(); + orderBefore.setId(1L); + orderBefore.setOrderStatus(OrderStatus.COOKING.name()); + Mockito.when(orderDao.findById(Mockito.anyLong())).thenReturn(Optional.empty()); + return orderBefore; + } + } + + @Nested + @DisplayName("이미 결재 완료인 경우 주문 상태 변경 실패") + public class ErrorOrderAlreadyComplete { + @Test + public void testCase() { + // given + final Order order = setup(); + final Order newOrder = new Order(); + newOrder.setOrderStatus(OrderStatus.MEAL.name()); + + // when - then + assertThrows(IllegalArgumentException.class, + () -> orderService.changeOrderStatus(order.getId(), newOrder)); + } + + private Order setup() { + final Order orderBefore = new Order(); + orderBefore.setId(1L); + orderBefore.setOrderStatus(OrderStatus.COMPLETION.name()); + Mockito.when(orderDao.findById(Mockito.anyLong())).thenReturn(Optional.of(orderBefore)); + return orderBefore; + } + } + } +} diff --git a/src/test/java/kitchenpos/ui/OrderRestControllerTest.java b/src/test/java/kitchenpos/ui/OrderRestControllerTest.java new file mode 100644 index 0000000000..f6c914a38b --- /dev/null +++ b/src/test/java/kitchenpos/ui/OrderRestControllerTest.java @@ -0,0 +1,189 @@ +package kitchenpos.ui; + +import com.fasterxml.jackson.databind.ObjectMapper; +import kitchenpos.domain.Order; +import kitchenpos.domain.OrderStatus; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; + +@WebMvcTest(OrderRestController.class) +@DisplayName("OrderRestController 클래스 테스트") +public class OrderRestControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private OrderRestController orderRestController; + + @Nested + @DisplayName("POST /api/orders") + public class PostMethod { + @Test + @DisplayName("성공적으로 주문을 등록하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + final Order order = setupSuccess(); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/orders") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(order)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + + // then + final Order orderResponse = + objectMapper.readValue(response.getContentAsString(), Order.class); + assertThat(orderResponse.getId()).isPositive(); + } + + private Order setupSuccess() { + final Order order = new Order(); + order.setId(1L); + Mockito.when(orderRestController.create(Mockito.any())).thenReturn(ResponseEntity.ok(order)); + return order; + } + + @Test + @DisplayName("주문을 등록하는데 싪해하면 400 상태 코드를 응답받는다") + public void errorBadRequest() throws Exception { + // given + final Order order = setupErrorBadRequest(); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/orders") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(order)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + private Order setupErrorBadRequest() { + final Order order = new Order(); + Mockito.when(orderRestController.create(Mockito.any())).thenReturn(ResponseEntity.badRequest().build()); + return order; + } + } + + @Nested + @DisplayName("GET /api/orders") + public class ListMethod { + @Test + @DisplayName("성공적으로 주문 목록을 조회하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + setupSuccess(); + + // when + MockHttpServletResponse response = mockMvc.perform(get("/api/orders") + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + } + + private void setupSuccess() { + final List orders = Stream.of(1, 2, 3) + .map(Long::new) + .map(id -> { + final Order order = new Order(); + order.setId(id); + return order; + }) + .collect(Collectors.toList()); + Mockito.when(orderRestController.list()).thenReturn(ResponseEntity.ok(orders)); + } + } + + @Nested + @DisplayName("PUT /api/orders/{orderId}/order-status") + public class PutMethod { + @Test + @DisplayName("성공적으로 주문 상태 변경을 하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + final Order order = setupSuccess(); + final Order newOrder = new Order(); + newOrder.setOrderStatus(OrderStatus.MEAL.name()); + + // when + MockHttpServletResponse response = mockMvc.perform( + put("/api/orders/" + order.getId() + "/order-status") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newOrder)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + + // then + final Order orderResponse = + objectMapper.readValue(response.getContentAsString(), Order.class); + assertThat(orderResponse.getOrderStatus()).isEqualTo(newOrder.getOrderStatus()); + } + + private Order setupSuccess() { + final Order orderAfter = new Order(); + orderAfter.setOrderStatus(OrderStatus.MEAL.name()); + Mockito.when(orderRestController.changeOrderStatus(Mockito.anyLong(), Mockito.any())) + .thenReturn(ResponseEntity.ok(orderAfter)); + final Order orderBefore = new Order(); + orderBefore.setId(1L); + return orderBefore; + } + + @Test + @DisplayName("주문 상태 변경하는데 실패하면 400 상태 코드를 응답받는다") + public void errorBadRequest() throws Exception { + // given + final Order order = setupErrorBadRequest(); + final Order newOrder = new Order(); + newOrder.setOrderStatus(OrderStatus.MEAL.name()); + + // when + MockHttpServletResponse response = mockMvc.perform( + put("/api/orders/" + order.getId() + "/order-status") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newOrder)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + private Order setupErrorBadRequest() { + final Order orderBefore = new Order(); + orderBefore.setId(1L); + Mockito.when(orderRestController.changeOrderStatus(Mockito.anyLong(), Mockito.any())) + .thenReturn(ResponseEntity.badRequest().build()); + return orderBefore; + } + } +} From 8647a96f5edd4223d5c465c25666955ef7b6c2f2 Mon Sep 17 00:00:00 2001 From: OptimistLabyrinth Date: Tue, 13 Dec 2022 23:19:34 +0900 Subject: [PATCH 10/10] =?UTF-8?q?test:=20TableRestController,=20TableServi?= =?UTF-8?q?ce=20=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +- .../application/TableServiceTest.java | 306 ++++++++++++++++++ .../ui/TableRestControllerTest.java | 249 ++++++++++++++ 3 files changed, 557 insertions(+), 5 deletions(-) create mode 100644 src/test/java/kitchenpos/application/TableServiceTest.java create mode 100644 src/test/java/kitchenpos/ui/TableRestControllerTest.java diff --git a/README.md b/README.md index 1047e0422e..3662f35cf9 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,6 @@ ### 상품 생성하기 (POST /api/products) - HTTP Response Code : 201 CREATED -- 가격 - - 가격은 반드시 있어야 한다 - - 가격은 0 이상의 정수여야 한다. ### 상품 리스트 조회 (GET /api/products) @@ -163,6 +160,8 @@ - 단체 손님으로 지정한 주문 테이블은 빈 테이블로 표시할 수 없다. - 조리중 또는 식사중 상태인 주문 테이블은 빈 테이블로 만들 수 없고, 계산 완료한 주문 테이블만 빈 테이블로 만들 수 있다. - 테이블에 앉아 있는 손님의 숫자를 기록할 수 있다. +- 테이블에는 항상 1명 이상의 손님이 앉아있다. +- 빈 테이블은 손님의 숫자를 변경할 수 없다. ## 테이블 생성 (POST /api/tables) @@ -179,8 +178,6 @@ ### 테이블의 손님 숫자 변경하기 (PUT /api/tables/{orderTableId}/number-of-guests) - HTTP Response Code : 200 OK -- 변경하려는 손님 숫자는 반드시 0 이상이어야 한다. -- 주문 테이블이 비어있으면 손님 숫자를 변경할 수 없다. ## 용어 사전 diff --git a/src/test/java/kitchenpos/application/TableServiceTest.java b/src/test/java/kitchenpos/application/TableServiceTest.java new file mode 100644 index 0000000000..c9155333bf --- /dev/null +++ b/src/test/java/kitchenpos/application/TableServiceTest.java @@ -0,0 +1,306 @@ +package kitchenpos.application; + +import kitchenpos.dao.OrderDao; +import kitchenpos.dao.OrderTableDao; +import kitchenpos.domain.OrderTable; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +@DisplayName("TableService 클래스 테스트") +public class TableServiceTest { + @Mock + private OrderDao orderDao; + @Mock + private OrderTableDao orderTableDao; + @InjectMocks + private TableService tableService; + + @Nested + @DisplayName("create 메서드 테스트") + public class CreateMethod { + @Nested + @DisplayName("주문 테이블 등록 성공") + public class Success { + @Test + public void testCase() { + // given + final OrderTable orderTable = setup(); + + // when + final OrderTable createdTable = tableService.create(orderTable); + + // then + assertAll( + () -> assertThat(createdTable.getId()).isPositive(), + () -> assertThat(createdTable.getTableGroupId()).isNull() + ); + } + + private OrderTable setup() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + orderTable.setTableGroupId(null); + Mockito.when(orderTableDao.save(Mockito.any())).thenReturn(orderTable); + return orderTable; + } + } + } + + @Nested + @DisplayName("list 메서드 테스트") + public class ListMethod { + @Nested + @DisplayName("주문 테이블 빈 목록 조회 성공") + public class Success { + @Test + public void testCase() { + // given + final List orderTables = setup(); + + // when + final List foundOrderTables = tableService.list(); + + // then + assertThat(foundOrderTables).hasSize(orderTables.size()); + } + + private List setup() { + final List orderTables = Arrays.asList(new OrderTable(), new OrderTable(), new OrderTable()); + Mockito.when(orderTableDao.findAll()).thenReturn(orderTables); + return orderTables; + } + } + + @Nested + @DisplayName("주문 테이블 빈 목록 조회 성공") + public class SuccessEmpty { + @Test + public void testCase() { + // given + setup(); + + // when + final List foundOrderTables = tableService.list(); + + // then + assertThat(foundOrderTables).hasSize(0); + } + + private void setup() { + final List orderTables = new ArrayList<>(); + Mockito.when(orderTableDao.findAll()).thenReturn(orderTables); + } + } + } + + @Nested + @DisplayName("changeEmpty 메서드 테스트") + public class ChangeEmptyMethod { + @Nested + @DisplayName("주문 테이블 빈 테이블로 지정 성공") + public class Success { + @Test + public void testCase() { + // given + final OrderTable orderTable = setup(); + final OrderTable newOrderTable = new OrderTable(); + newOrderTable.setEmpty(false); + + // when + final OrderTable changedOrderTable = tableService.changeEmpty(orderTable.getId(), newOrderTable); + + // then + assertThat(changedOrderTable.isEmpty()).isEqualTo(newOrderTable.isEmpty()); + } + + private OrderTable setup() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + Mockito.when(orderTableDao.findById(Mockito.anyLong())).thenReturn(Optional.of(orderTable)); + orderTable.setTableGroupId(null); + Mockito.when(orderDao.existsByOrderTableIdAndOrderStatusIn(Mockito.anyLong(), Mockito.anyList())) + .thenReturn(false); + orderTable.setEmpty(false); + Mockito.when(orderTableDao.save(Mockito.any())).thenReturn(orderTable); + return orderTable; + } + } + + @Nested + @DisplayName("주문 테이블 id 로 실제 주문 테이블을 찾을 수 없으면 주문 테이블 빈 테이블로 지정 실패") + public class ErrorOrderTableNotFoundById { + @Test + public void testCase() { + // given + final OrderTable orderTable = setup(); + final OrderTable newOrderTable = new OrderTable(); + newOrderTable.setEmpty(false); + + // when - then + assertThrows(IllegalArgumentException.class, + () -> tableService.changeEmpty(orderTable.getId(), newOrderTable)); + } + + private OrderTable setup() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + Mockito.when(orderTableDao.findById(Mockito.anyLong())).thenReturn(Optional.empty()); + return orderTable; + } + } + + @Nested + @DisplayName("주문 테이블 id 로 검색해서 조리중이거나 식사중인 주문이 있을 경우 주문 테이블 빈 테이블로 지정 실패") + public class ErrorOrderCookingOrEating { + @Test + public void testCase() { + // given + final OrderTable orderTable = setup(); + final OrderTable newOrderTable = new OrderTable(); + newOrderTable.setEmpty(false); + + // when - then + assertThrows(IllegalArgumentException.class, + () -> tableService.changeEmpty(orderTable.getId(), newOrderTable)); + } + + private OrderTable setup() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + Mockito.when(orderTableDao.findById(Mockito.anyLong())).thenReturn(Optional.of(orderTable)); + orderTable.setTableGroupId(null); + Mockito.when(orderDao.existsByOrderTableIdAndOrderStatusIn(Mockito.anyLong(), Mockito.anyList())) + .thenReturn(true); + return orderTable; + } + } + } + + @Nested + @DisplayName("changeNumberOfGuests 메서드 테스트") + public class ChangeNumberOfGuestsMethod { + @Nested + @DisplayName("주문 테이블에 앉아 있는 손님의 숫자 변경 성공") + public class Success { + private final int NUMBER_OF_GUESTS_AFTER = 5; + + @Test + public void testCase() { + // given + final OrderTable orderTable = setup(); + final OrderTable newOrderTable = new OrderTable(); + newOrderTable.setNumberOfGuests(NUMBER_OF_GUESTS_AFTER); + + // when + final OrderTable changedOrderTable = tableService.changeNumberOfGuests(orderTable.getId(), + newOrderTable); + + // then + assertThat(changedOrderTable.getNumberOfGuests()).isEqualTo(newOrderTable.getNumberOfGuests()); + } + + private OrderTable setup() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + final int NUMBER_OF_GUESTS_BEFORE = 3; + orderTable.setNumberOfGuests(NUMBER_OF_GUESTS_BEFORE); + orderTable.setEmpty(false); + Mockito.when(orderTableDao.findById(Mockito.anyLong())).thenReturn(Optional.of(orderTable)); + orderTable.setNumberOfGuests(NUMBER_OF_GUESTS_AFTER); + Mockito.when(orderTableDao.save(Mockito.any())).thenReturn(orderTable); + return orderTable; + } + } + + @Nested + @DisplayName("새롭게 변경하려는 손님의 숫자가 음수이면 변경 실패") + public class ErrorNumberOfGuestsMinus { + @Test + public void testCase() { + // given + final OrderTable orderTable = setup(); + final OrderTable newOrderTable = new OrderTable(); + newOrderTable.setNumberOfGuests(-1); + + // when - then + assertThrows(IllegalArgumentException.class, + () -> tableService.changeNumberOfGuests(orderTable.getId(), newOrderTable)); + } + + private OrderTable setup() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + return orderTable; + } + } + + @Nested + @DisplayName("주문 테이블 id 로 실제 주문 테이블을 찾을 수 없으면 주문 테이블에 앉아 있는 손님의 숫자 변경 성공") + public class ErrorOrderTableNotFound { + @Test + public void testCase() { + // given + final OrderTable orderTable = setup(); + final OrderTable newOrderTable = new OrderTable(); + final int NUMBER_OF_GUESTS_AFTER = 5; + newOrderTable.setNumberOfGuests(NUMBER_OF_GUESTS_AFTER); + + // when - then + assertThrows(IllegalArgumentException.class, + () -> tableService.changeNumberOfGuests(orderTable.getId(), newOrderTable)); + } + + private OrderTable setup() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + final int NUMBER_OF_GUESTS_BEFORE = 3; + orderTable.setNumberOfGuests(NUMBER_OF_GUESTS_BEFORE); + Mockito.when(orderTableDao.findById(Mockito.anyLong())).thenReturn(Optional.empty()); + return orderTable; + } + } + + @Nested + @DisplayName("주문 테이블이 빈 테이블이면 주문 테이블에 앉아 있는 손님의 숫자 변경 성공") + public class ErrorOrderTableEmpty { + @Test + public void testCase() { + // given + final OrderTable orderTable = setup(); + final OrderTable newOrderTable = new OrderTable(); + final int NUMBER_OF_GUESTS_AFTER = 5; + newOrderTable.setNumberOfGuests(NUMBER_OF_GUESTS_AFTER); + + // when - then + assertThrows(IllegalArgumentException.class, + () -> tableService.changeNumberOfGuests(orderTable.getId(), newOrderTable)); + } + + private OrderTable setup() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + final int NUMBER_OF_GUESTS_BEFORE = 3; + orderTable.setNumberOfGuests(NUMBER_OF_GUESTS_BEFORE); + orderTable.setEmpty(true); + Mockito.when(orderTableDao.findById(Mockito.anyLong())).thenReturn(Optional.empty()); + return orderTable; + } + } + } +} diff --git a/src/test/java/kitchenpos/ui/TableRestControllerTest.java b/src/test/java/kitchenpos/ui/TableRestControllerTest.java new file mode 100644 index 0000000000..451d9b88f4 --- /dev/null +++ b/src/test/java/kitchenpos/ui/TableRestControllerTest.java @@ -0,0 +1,249 @@ +package kitchenpos.ui; + +import com.fasterxml.jackson.databind.ObjectMapper; +import kitchenpos.domain.OrderTable; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; + +@WebMvcTest(TableRestController.class) +@DisplayName("TableRestController 클래스 테스트") +public class TableRestControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private TableRestController tableRestController; + + @Nested + @DisplayName("POST /api/tables") + public class PostMethod { + @Test + @DisplayName("성공적으로 주문 테이블을 등록하면 201 상태 코드를 응답받는다") + public void success() throws Exception { + // given + final OrderTable orderTable = setupSuccess(); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/tables") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(orderTable)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.CREATED.value()); + + // then + final OrderTable orderTableResponse = + objectMapper.readValue(response.getContentAsString(), OrderTable.class); + assertThat(orderTableResponse.getId()).isEqualTo(orderTable.getId()); + } + + private OrderTable setupSuccess() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + final URI uri = URI.create("/api/tables/" + orderTable.getId()); + Mockito.when(tableRestController.create(Mockito.any())) + .thenReturn(ResponseEntity.created(uri).body(orderTable)); + return orderTable; + } + + @Test + @DisplayName("주문 테이블을 등록하는데 실패하면 400 상태 코드를 응답받는다") + public void badRequest() throws Exception { + // given + final OrderTable orderTable = setupBadRequest(); + + // when + MockHttpServletResponse response = mockMvc.perform(post("/api/tables") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(orderTable)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + private OrderTable setupBadRequest() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + final URI uri = URI.create("/api/tables/" + orderTable.getId()); + Mockito.when(tableRestController.create(Mockito.any())).thenReturn(ResponseEntity.badRequest().build()); + return orderTable; + } + } + + @Nested + @DisplayName("GET /api/tables") + public class GetMethod { + @Test + @DisplayName("성공적으로 주문 테이블을 조회하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + setup(); + + // when + MockHttpServletResponse response = mockMvc.perform(get("/api/tables") + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + } + + private void setup() { + final List orderTables = Arrays.asList(new OrderTable(), new OrderTable(), new OrderTable()); + Mockito.when(tableRestController.list()).thenReturn(ResponseEntity.ok(orderTables)); + } + } + + @Nested + @DisplayName("PUT /api/tables/{orderTableId}/empty") + public class SetEmpty { + @Test + @DisplayName("성공적으로 주문 테이블을 빈 테이블로 표시하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + final OrderTable orderTable = setupSuccess(); + final OrderTable newOrderTable = new OrderTable(); + newOrderTable.setEmpty(false); + + // when + MockHttpServletResponse response = mockMvc + .perform(put("/api/tables/" + orderTable.getId() + "/empty") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newOrderTable)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + } + + private OrderTable setupSuccess() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + orderTable.setEmpty(false); + Mockito.when(tableRestController.changeEmpty(Mockito.anyLong(), Mockito.any())) + .thenReturn(ResponseEntity.ok(orderTable)); + return orderTable; + } + + @Test + @DisplayName("성공적으로 주문 테이블을 빈 테이블로 표시하면 200 상태 코드를 응답받는다") + public void badRequest() throws Exception { + // given + final OrderTable orderTable = setupBadRequest(); + final OrderTable newOrderTable = new OrderTable(); + newOrderTable.setEmpty(false); + + // when + MockHttpServletResponse response = mockMvc + .perform(put("/api/tables/" + orderTable.getId() + "/empty") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newOrderTable)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + private OrderTable setupBadRequest() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + orderTable.setEmpty(false); + Mockito.when(tableRestController.changeEmpty(Mockito.anyLong(), Mockito.any())) + .thenReturn(ResponseEntity.badRequest().build()); + return orderTable; + } + } + + @Nested + @DisplayName("PUT /api/tables/{orderTableId}/number-of-guests") + public class SetNumberOfGuests { + private final int NUMBER_OF_GUESTS_TO_CHANGE = 5; + + @Test + @DisplayName("성공적으로 주문 테이블에 앉은 손님의 숫자를 변경하면 200 상태 코드를 응답받는다") + public void success() throws Exception { + // given + final OrderTable orderTable = setupSuccess(); + final OrderTable newOrderTable = new OrderTable(); + newOrderTable.setNumberOfGuests(NUMBER_OF_GUESTS_TO_CHANGE); + + // when + MockHttpServletResponse response = mockMvc + .perform(put("/api/tables/" + orderTable.getId() + "/number-of-guests") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newOrderTable)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + + // then + final OrderTable orderTableResponse = + objectMapper.readValue(response.getContentAsString(), OrderTable.class); + assertThat(orderTableResponse.getNumberOfGuests()).isEqualTo(newOrderTable.getNumberOfGuests()); + } + + private OrderTable setupSuccess() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + orderTable.setNumberOfGuests(NUMBER_OF_GUESTS_TO_CHANGE); + Mockito.when(tableRestController.changeNumberOfGuests(Mockito.anyLong(), Mockito.any())) + .thenReturn(ResponseEntity.ok(orderTable)); + return orderTable; + } + + @Test + @DisplayName("성공적으로 주문 테이블에 앉은 손님의 숫자를 변경하면 200 상태 코드를 응답받는다") + public void badRequest() throws Exception { + // given + final OrderTable orderTable = setupBadRequest(); + final OrderTable newOrderTable = new OrderTable(); + newOrderTable.setNumberOfGuests(NUMBER_OF_GUESTS_TO_CHANGE); + + // when + MockHttpServletResponse response = mockMvc + .perform(put("/api/tables/" + orderTable.getId() + "/number-of-guests") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newOrderTable)) + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + private OrderTable setupBadRequest() { + final OrderTable orderTable = new OrderTable(); + orderTable.setId(1L); + orderTable.setNumberOfGuests(NUMBER_OF_GUESTS_TO_CHANGE); + Mockito.when(tableRestController.changeNumberOfGuests(Mockito.anyLong(), Mockito.any())) + .thenReturn(ResponseEntity.badRequest().build()); + return orderTable; + } + } +}