diff --git a/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/controller/FolderControllerTest.java b/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/controller/FolderControllerTest.java new file mode 100644 index 0000000..2f1e050 --- /dev/null +++ b/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/controller/FolderControllerTest.java @@ -0,0 +1,216 @@ +package com.serverstudy.todolist.controller; + +import com.serverstudy.todolist.dto.request.FolderReq.FolderPatch; +import com.serverstudy.todolist.dto.request.FolderReq.FolderPost; +import com.serverstudy.todolist.dto.response.FolderRes; +import com.serverstudy.todolist.exception.CustomException; +import com.serverstudy.todolist.exception.ErrorCode; +import com.serverstudy.todolist.service.FolderService; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +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.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(FolderController.class) +class FolderControllerTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + FolderService folderService; + + @Nested + class postFolder_메서드는 { + @Test + void 폴더_이름과_유저_기본키가_주어지면_폴더를_생성하고_created_응답과_폴더_기본키를_반환한다() throws Exception { + // given + Long userId = 1L; + String folderPostJson = """ + { + "name": "새로운 폴더" + } + """; + Long folderId = 1L; + given(folderService.create(any(FolderPost.class), eq(userId))).willReturn(folderId); + + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/folder?userId={userId}", userId) + .contentType(MediaType.APPLICATION_JSON) + .content(folderPostJson)) + // then + .andExpect(status().isCreated()) + .andExpect(content().string(folderId.toString())); + } + @Test + void 만약_없는_유저의_기본키가_주어지면_USER_NOT_FOUND_예외가_반환된다() throws Exception { + // given + Long nonExistingUserId = 404L; + String folderPostJson = """ + { + "name": "새로운 폴더" + } + """; + + given(folderService.create(any(FolderPost.class), eq(nonExistingUserId))).willThrow(new CustomException(ErrorCode.USER_NOT_FOUND)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/folder?userId={nonExistingUserId}", nonExistingUserId) + .contentType(MediaType.APPLICATION_JSON) + .content(folderPostJson)) + // then + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ErrorCode.USER_NOT_FOUND.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.USER_NOT_FOUND.getMessage())); + } + @Test + void 만약_중복된_폴더_이름이_주어지면_DUPLICATE_FOLDER_NAME_예외가_반환된다() throws Exception { + // given + Long userId = 1L; + String folderPostJson = """ + { + "name": "중복된 폴더" + } + """; + + given(folderService.create(any(FolderPost.class), eq(userId))).willThrow(new CustomException(ErrorCode.DUPLICATE_FOLDER_NAME)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/folder?userId={userId}", userId) + .contentType(MediaType.APPLICATION_JSON) + .content(folderPostJson)) + // then + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(ErrorCode.DUPLICATE_FOLDER_NAME.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.DUPLICATE_FOLDER_NAME.getMessage())); + } + } + + @Nested + class getFoldersByUser_메서드는 { + + // 폴더 목록 조회 성공 + @Test + void 유저_기본키가_주어지면_성공_응답과_폴더_응답_객체_리스트를_반환한다() throws Exception { + // given + Long userId = 1L; + List folderList = List.of( + FolderRes.builder().folderId(1L).name("폴더1").todoCount(3).build(), + FolderRes.builder().folderId(2L).name("폴더2").todoCount(5).build() + ); + + given(folderService.getAllWithTodoCount(userId)).willReturn(folderList); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/folder?userId={userId}", userId)) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].folderId").value(folderList.get(0).getFolderId())) + .andExpect(jsonPath("$[0].name").value(folderList.get(0).getName())) + .andExpect(jsonPath("$[0].todoCount").value(folderList.get(0).getTodoCount())) + .andExpect(jsonPath("$[1].folderId").value(folderList.get(1).getFolderId())) + .andExpect(jsonPath("$[1].name").value(folderList.get(1).getName())) + .andExpect(jsonPath("$[1].todoCount").value(folderList.get(1).getTodoCount())); + } + } + + @Nested + class patchFolder_메서드는 { + @Test + void 수정할_폴더_이름과_유저_기본키가_주어지면_수정_후_성공_응답과_폴더_기본키를_반환한다() throws Exception { + // given + Long folderId = 1L; + String folderPatchJson = """ + { + "name": "수정된 폴더" + } + """; + + Long modifiedFolderId = 1L; + given(folderService.modify(any(FolderPatch.class), eq(folderId))).willReturn(modifiedFolderId); + + // when + mockMvc.perform(MockMvcRequestBuilders + .patch("/api/folder/{folderId}", folderId) + .contentType(MediaType.APPLICATION_JSON) + .content(folderPatchJson)) + // then + .andExpect(status().isOk()) + .andExpect(content().string(modifiedFolderId.toString())); + } + @Test + void 만약_없는_폴더의_기본키가_주어지면_FOLDER_NOT_FOUND_예외가_반환된다() throws Exception { + // given + Long nonExistingFolderId = 1L; + String folderPatchJson = """ + { + "name": "수정된 폴더" + } + """; + + given(folderService.modify(any(FolderPatch.class), eq(nonExistingFolderId))).willThrow(new CustomException(ErrorCode.FOLDER_NOT_FOUND)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .patch("/api/folder/{nonExistingFolderId}", nonExistingFolderId) + .contentType(MediaType.APPLICATION_JSON) + .content(folderPatchJson)) + // then + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ErrorCode.FOLDER_NOT_FOUND.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.FOLDER_NOT_FOUND.getMessage())); + } + @Test + void 만약_중복된_폴더_이름이_주어지면_DUPLICATE_FOLDER_NAME_예외가_반환된다() throws Exception { + // given + Long folderId = 1L; + String folderPatchJson = """ + { + "name": "중복된 폴더" + } + """; + + given(folderService.modify(any(FolderPatch.class), eq(folderId))).willThrow(new CustomException(ErrorCode.DUPLICATE_FOLDER_NAME)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .patch("/api/folder/{folderId}", folderId) + .contentType(MediaType.APPLICATION_JSON) + .content(folderPatchJson)) + // then + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(ErrorCode.DUPLICATE_FOLDER_NAME.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.DUPLICATE_FOLDER_NAME.getMessage())); + } + } + + @Nested + class deleteFolder_메서드는 { + @Test + void 폴더_기본키가_주어지면_해당_폴더를_삭제한다() throws Exception { + // given + Long folderId = 1L; + + // when + mockMvc.perform(MockMvcRequestBuilders + .delete("/api/folder/{folderId}", folderId)) + // then + .andExpect(status().isNoContent()); + } + } +} \ No newline at end of file diff --git a/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/controller/TodoControllerTest.java b/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/controller/TodoControllerTest.java new file mode 100644 index 0000000..b7cdc22 --- /dev/null +++ b/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/controller/TodoControllerTest.java @@ -0,0 +1,366 @@ +package com.serverstudy.todolist.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.serverstudy.todolist.domain.enums.Priority; +import com.serverstudy.todolist.domain.enums.Progress; +import com.serverstudy.todolist.dto.request.TodoReq.TodoGet; +import com.serverstudy.todolist.dto.request.TodoReq.TodoPost; +import com.serverstudy.todolist.dto.request.TodoReq.TodoPut; +import com.serverstudy.todolist.dto.response.TodoRes; +import com.serverstudy.todolist.exception.CustomException; +import com.serverstudy.todolist.exception.ErrorCode; +import com.serverstudy.todolist.service.TodoService; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +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.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(TodoController.class) +class TodoControllerTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + TodoService todoService; + + ObjectMapper objectMapper = new ObjectMapper(); + + @Nested + class postTodo_메서드는 { + + @Test + void 투두를_생성하고_created_응답과_투두_기본키를_반환한다() throws Exception { + // given + Long userId = 1L; + String todoPostJson = """ + { + "title": "컴퓨터공학개론 레포트", + "description": "주제: 컴퓨터공학이란 무엇인가?, 분량: 3장 이상", + "deadline": "2024-05-15T23:59:00Z", + "priority": "NONE", + "progress": "TODO", + "folderId": 1 + } + """; + Long todoId = 1L; + given(todoService.create(any(TodoPost.class), eq(userId))).willReturn(todoId); + + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/todo?userId={userId}", userId) + .contentType(MediaType.APPLICATION_JSON) + .content(todoPostJson)) + // then + .andExpect(status().isCreated()) + .andExpect(content().string(todoId.toString())); + } + @Test + void 만약_존재하지_않는_폴더_기본키가_주어지면_FOLDER_NOT_FOUND_예외를_반환한다() throws Exception { + // given + Long userId = 1L; + String todoPostJson = """ + { + "title": "컴퓨터공학개론 레포트", + "description": "주제: 컴퓨터공학이란 무엇인가?, 분량: 3장 이상", + "deadline": "2024-05-15T23:59:00Z", + "priority": "NONE", + "progress": "TODO", + "folderId": 404 + } + """; + given(todoService.create(any(TodoPost.class), eq(userId))).willThrow(new CustomException(ErrorCode.FOLDER_NOT_FOUND)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/todo?userId={userId}", userId) + .contentType(MediaType.APPLICATION_JSON) + .content(todoPostJson)) + // then + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ErrorCode.FOLDER_NOT_FOUND.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.FOLDER_NOT_FOUND.getMessage())); + } + } + + @Nested + class getTodosByRequirements_메서드는 { + + @Test + void 조건에_맞는_투두를_조회하고_성공_응답과_투두_응답_객체_리스트를_반환한다() throws Exception { + // given + Long userId = 1L; + String todoGetJson = """ + { + "priority": "NONE", + "progress": "TODO", + "isDeleted": false, + "folderId": 1 + } + """; + List todoList = List.of( + TodoRes.builder().id(1L).title("Todo1").priority(Priority.NONE).progress(Progress.TODO).folderId(1L).build(), + TodoRes.builder().id(2L).title("Todo2").priority(Priority.NONE).progress(Progress.TODO).folderId(1L).build() + ); + given(todoService.findAllByConditions(any(TodoGet.class), eq(userId))).willReturn(todoList); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/todo?userId={userId}", userId) + .contentType(MediaType.APPLICATION_JSON) + .queryParam("priority", "NONE") + .queryParam("progress", "TODO") + .queryParam("isDeleted", "false") + .queryParam("folderId", "1")) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(todoList.get(0).getId())) + .andExpect(jsonPath("$[0].title").value(todoList.get(0).getTitle())) + .andExpect(jsonPath("$[0].priority").value(todoList.get(0).getPriority().toString())) + .andExpect(jsonPath("$[0].progress").value(todoList.get(0).getProgress().toString())) + .andExpect(jsonPath("$[0].folderId").value(todoList.get(0).getFolderId())) + .andExpect(jsonPath("$[1].id").value(todoList.get(1).getId())) + .andExpect(jsonPath("$[1].title").value(todoList.get(1).getTitle())) + .andExpect(jsonPath("$[1].priority").value(todoList.get(1).getPriority().toString())) + .andExpect(jsonPath("$[1].progress").value(todoList.get(1).getProgress().toString())) + .andExpect(jsonPath("$[1].folderId").value(todoList.get(1).getFolderId())); + } + } + + @Nested + class putTodo_메서드는 { + @Test + void 투두를_수정하고_성공_응답과_투두_기본키를_반환한다() throws Exception { + // given + Long todoId = 1L; + String todoPutJson = """ + { + "title": "앱센터 발표", + "description": "주제: 스프링이란?, 비고: GitHub에 업로드 ", + "deadline": "2024-05-20T12:00:00Z", + "priority": "PRIMARY", + "progress": "DOING", + "folderId": 1 + } + """; + given(todoService.update(any(TodoPut.class), eq(todoId))).willReturn(todoId); + + // when + mockMvc.perform(MockMvcRequestBuilders + .put("/api/todo/{todoId}", todoId) + .contentType(MediaType.APPLICATION_JSON) + .content(todoPutJson)) + // then + .andExpect(status().isOk()) + .andExpect(content().string(todoId.toString())); + } + @Test + void 만약_존재하지_않는_투두_기본키가_주어지면_TODO_NOT_FOUND_예외를_반환한다() throws Exception { + // given + Long nonExistingTodoId = 404L; + String todoPutJson = """ + { + "title": "앱센터 발표", + "description": "주제: 스프링이란?, 비고: GitHub에 업로드 ", + "deadline": "2024-05-20T12:00:00Z", + "priority": "PRIMARY", + "progress": "DOING", + "folderId": 1 + } + """; + given(todoService.update(any(TodoPut.class), eq(nonExistingTodoId))).willThrow(new CustomException(ErrorCode.TODO_NOT_FOUND)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .put("/api/todo/{nonExistingTodoId}", nonExistingTodoId) + .contentType(MediaType.APPLICATION_JSON) + .content(todoPutJson)) + // then + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ErrorCode.TODO_NOT_FOUND.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.TODO_NOT_FOUND.getMessage())); + } + @Test + void 만약_존재하지_않는_폴더_기본키가_주어지면_FOLDER_NOT_FOUND_예외를_반환한다() throws Exception { + // given + Long todoId = 1L; + String todoPutJson = """ + { + "title": "앱센터 발표", + "description": "주제: 스프링이란?, 비고: GitHub에 업로드 ", + "deadline": "2024-05-20T12:00:00Z", + "priority": "PRIMARY", + "progress": "DOING", + "folderId": 404 + } + """; + given(todoService.update(any(TodoPut.class), eq(todoId))).willThrow(new CustomException(ErrorCode.FOLDER_NOT_FOUND)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .put("/api/todo/{todoId}", todoId) + .contentType(MediaType.APPLICATION_JSON) + .content(todoPutJson)) + // then + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ErrorCode.FOLDER_NOT_FOUND.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.FOLDER_NOT_FOUND.getMessage())); + } + } + + @Nested + class switchTodoProgress_메서드는 { + @Test + void 투두_진행_상황을_변경하고_성공_응답과_투두_기본키를_반환한다() throws Exception { + // given + Long todoId = 1L; + given(todoService.switchProgress(eq(todoId))).willReturn(todoId); + + // when + mockMvc.perform(MockMvcRequestBuilders + .patch("/api/todo/{todoId}/progress", todoId)) + // then + .andExpect(status().isOk()) + .andExpect(content().string(todoId.toString())); + } + @Test + void 만약_존재하지_않는_투두_기본키가_주어지면_TODO_NOT_FOUND_예외를_반환한다() throws Exception { + // given + Long todoId = 1L; + given(todoService.switchProgress(eq(todoId))).willThrow(new CustomException(ErrorCode.TODO_NOT_FOUND)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .patch("/api/todo/{todoId}/progress", todoId)) + // then + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ErrorCode.TODO_NOT_FOUND.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.TODO_NOT_FOUND.getMessage())); + } + } + + @Nested + class patchTodoFolder_메서드는 { + @Test + void 투두_폴더를_변경하고_성공_응답과_투두_기본키를_반환한다() throws Exception { + // given + Long todoId = 1L; + String todoFolderPatchJson = """ + { + "folderId": 2 + } + """; + given(todoService.moveFolder(anyLong(), eq(todoId))).willReturn(todoId); + + // when + mockMvc.perform(MockMvcRequestBuilders + .patch("/api/todo/{todoId}/folder", todoId) + .contentType(MediaType.APPLICATION_JSON) + .content(todoFolderPatchJson)) + // then + .andExpect(status().isOk()) + .andExpect(content().string(todoId.toString())); + } + @Test + void 만약_존재하지_않는_투두_기본키가_주어지면_TODO_NOT_FOUND_예외를_반환한다() throws Exception { + // given + Long todoId = 1L; + String todoFolderPatchJson = """ + { + "folderId": 2 + } + """; + given(todoService.moveFolder(anyLong(), eq(todoId))).willThrow(new CustomException(ErrorCode.TODO_NOT_FOUND)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .patch("/api/todo/{todoId}/folder", todoId) + .contentType(MediaType.APPLICATION_JSON) + .content(todoFolderPatchJson)) + // then + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ErrorCode.TODO_NOT_FOUND.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.TODO_NOT_FOUND.getMessage())); + } + @Test + void 만약_존재하지_않는_폴더_기본키가_주어지면_FOLDER_NOT_FOUND_예외를_반환한다() throws Exception { + // given + Long todoId = 1L; + String todoFolderPatchJson = """ + { + "folderId": 2 + } + """; + given(todoService.moveFolder(anyLong(), eq(todoId))).willThrow(new CustomException(ErrorCode.FOLDER_NOT_FOUND)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .patch("/api/todo/{todoId}/folder", todoId) + .contentType(MediaType.APPLICATION_JSON) + .content(todoFolderPatchJson)) + // then + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ErrorCode.FOLDER_NOT_FOUND.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.FOLDER_NOT_FOUND.getMessage())); + } + } + + @Nested + class deleteTodo_메서드는 { + @Test + void 투두_임시_삭제_후_성공_응답과_투두_기본키를_반환한다() throws Exception { + // given + Long todoId = 1L; + Boolean restore = true; + given(todoService.delete(eq(todoId), eq(restore))).willReturn(todoId); + + // when + mockMvc.perform(MockMvcRequestBuilders + .delete("/api/todo/{todoId}?restore={restore}", todoId, restore)) + // then + .andExpect(status().isOk()) + .andExpect(content().string(todoId.toString())); + } + + @Test + void 투두_영구_삭제_성공_후_noContent_응답을_반환한다() throws Exception { + // given + Long todoId = 1L; + Boolean restore = false; + given(todoService.delete(eq(todoId), eq(restore))).willReturn(null); + + // when + mockMvc.perform(MockMvcRequestBuilders + .delete("/api/todo/{todoId}?restore={restore}", todoId, restore)) + // then + .andExpect(status().isNoContent()); + } + + @Test + void 만약_존재하지_않는_투두_기본키가_주어지면_TODO_NOT_FOUND_예외를_반환한다() throws Exception { + // given + Long todoId = 1L; + Boolean restore = true; + given(todoService.delete(eq(todoId), eq(restore))).willThrow(new CustomException(ErrorCode.TODO_NOT_FOUND)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .delete("/api/todo/{todoId}?restore={restore}", todoId, restore)) + // then + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ErrorCode.TODO_NOT_FOUND.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.TODO_NOT_FOUND.getMessage())); + } + } + +} \ No newline at end of file diff --git a/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/controller/UserControllerTest.java b/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/controller/UserControllerTest.java new file mode 100644 index 0000000..d7a0a20 --- /dev/null +++ b/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/controller/UserControllerTest.java @@ -0,0 +1,352 @@ +package com.serverstudy.todolist.controller; + +import com.serverstudy.todolist.dto.request.UserReq; +import com.serverstudy.todolist.dto.request.UserReq.UserPatch; +import com.serverstudy.todolist.dto.response.UserRes; +import com.serverstudy.todolist.exception.CustomException; +import com.serverstudy.todolist.exception.ErrorCode; +import com.serverstudy.todolist.service.UserService; +import jakarta.validation.ConstraintViolationException; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +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.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(UserController.class) +class UserControllerTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + UserService userService; + + @Nested + class postUser_메서드는 { + @Test + void 이메일_비밀번호_닉네임이_주어지면_유저_기본키_HTTP_응답이_반환된다() throws Exception { + // given + String userPostJson = """ + { + "email": "success@email.com", + "password": "successPWD123", + "nickname": "ex성공1" + } + """; + Long userId = 1L; + given(userService.join(any(UserReq.UserPost.class))).willReturn(userId); + + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/user") + .contentType(MediaType.APPLICATION_JSON) + .content(userPostJson)) + // then + //.andDo(MockMvcResultHandlers.print()) + .andExpect(status().isCreated()) + .andExpect(content().string(userId.toString())); + } + @Test + void 만약_잘못된_형식의_이메일이_주어지면_유효성_검사_예외가_반환된다() throws Exception { + // given + String invalidEmail = "NoAtandNoDotcom"; + String userPostJson = """ + { + "email": "%s", + "password": "successPWD123", + "nickname": "ex성공1" + } + """.formatted(invalidEmail); + + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/user") + .contentType(MediaType.APPLICATION_JSON) + .content(userPostJson)) + // then + .andExpect(status().isBadRequest()) + .andExpect(result -> { + MethodArgumentNotValidException exception = (MethodArgumentNotValidException) result.getResolvedException(); + assertThat(exception).isNotNull(); + BindingResult bindingResult = exception.getBindingResult(); + assertThat(bindingResult.getFieldErrorCount()).isEqualTo(1); + FieldError fieldError = bindingResult.getFieldError("email"); + assertThat(fieldError).isNotNull(); + assertThat(fieldError.getRejectedValue()).isEqualTo(invalidEmail); + }); + //.andExpect(result -> assertInstanceOf(MethodArgumentNotValidException.class, result.getResolvedException())); + } + @Test + void 만약_잘못된_형식의_비밀번호가_주어지면_유효성_검사_예외가_반환된다() throws Exception { + // given + String invalidPassword = "noNumberPassword"; + String userPostJson = """ + { + "email": "success@email.com", + "password": "%s", + "nickname": "ex성공1" + } + """.formatted(invalidPassword); + + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/user") + .contentType(MediaType.APPLICATION_JSON) + .content(userPostJson)) + // then + .andExpect(status().isBadRequest()) + .andExpect(result -> { + MethodArgumentNotValidException exception = (MethodArgumentNotValidException) result.getResolvedException(); + assertThat(exception).isNotNull(); + BindingResult bindingResult = exception.getBindingResult(); + assertThat(bindingResult.getFieldErrorCount()).isEqualTo(1); + FieldError fieldError = bindingResult.getFieldError("password"); + assertThat(fieldError).isNotNull(); + assertThat(fieldError.getRejectedValue()).isEqualTo(invalidPassword); + }); + } + @Test + void 만약_잘못된_형식의_닉네임이_주어지면_유효성_검사_예외가_반환된다() throws Exception { + // given + String invalidNickname = "특수문자닉네임!@#"; + String userPostJson = """ + { + "email": "success@email.com", + "password": "successPWD123", + "nickname": "%s" + } + """.formatted(invalidNickname); + + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/user") + .contentType(MediaType.APPLICATION_JSON) + .content(userPostJson)) + // then + .andExpect(status().isBadRequest()) + .andExpect(result -> { + MethodArgumentNotValidException exception = (MethodArgumentNotValidException) result.getResolvedException(); + assertThat(exception).isNotNull(); + BindingResult bindingResult = exception.getBindingResult(); + assertThat(bindingResult.getFieldErrorCount()).isEqualTo(1); + FieldError fieldError = bindingResult.getFieldError("nickname"); + assertThat(fieldError).isNotNull(); + assertThat(fieldError.getRejectedValue()).isEqualTo(invalidNickname); + }); + } + @Test + void 만약_중복된_이메일이_주어지면_DUPLICATE_USER_EMAIL_예외가_반환된다() throws Exception { + // given + String userPostJson = """ + { + "email": "duplicated@email.com", + "password": "successPWD123", + "nickname": "ex성공1" + } + """; + willThrow(new CustomException(ErrorCode.DUPLICATE_USER_EMAIL)).given(userService).join(any(UserReq.UserPost.class)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/user") + .contentType(MediaType.APPLICATION_JSON) + .content(userPostJson)) + // then + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(ErrorCode.DUPLICATE_USER_EMAIL.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.DUPLICATE_USER_EMAIL.getMessage())); + } + } + + @Nested + class checkUserEmail_메서드는 { + @Test + void 이메일이_주어지면_이메일_HTTP_응답을_반환한다() throws Exception { + // given + String email = "success@email.com"; + willDoNothing().given(userService).checkEmailDuplicated(email); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/user/check-email") + .contentType(MediaType.APPLICATION_JSON) + .queryParam("email", email)) + // then + .andExpect(status().isOk()) + .andExpect(content().string(email)); + verify(userService, times(1)).checkEmailDuplicated(email); + } + @Test + void 만약_잘못된_형식의_이메일이_주어지면_유효성_검사_예외가_반환된다() throws Exception { + // given + String invalidEmail = "NoAtandNoDotcom"; + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/user/check-email") + .contentType(MediaType.APPLICATION_JSON) + .queryParam("email", invalidEmail)) + // then + .andExpect(status().isBadRequest()) + .andExpect(result -> { + ConstraintViolationException exception = (ConstraintViolationException) result.getResolvedException(); + assertThat(exception).isNotNull(); + assertThat(exception.getMessage()).contains("이메일 형식이 올바르지 않습니다."); + }); + //.andExpect(result -> assertInstanceOf(ConstraintViolationException.class, result.getResolvedException())); + } + @Test + void 만약_중복된_이메일이_주어지면_DUPLICATE_USER_EMAIL_예외가_반환된다() throws Exception { + // given + String duplicatedEmail = "duplicated@email.com"; + willThrow(new CustomException(ErrorCode.DUPLICATE_USER_EMAIL)).given(userService).checkEmailDuplicated(duplicatedEmail); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/user/check-email") + .contentType(MediaType.APPLICATION_JSON) + .queryParam("email", duplicatedEmail)) + // then + .andExpect(status().isConflict()) + .andExpect(jsonPath("$.code").value(ErrorCode.DUPLICATE_USER_EMAIL.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.DUPLICATE_USER_EMAIL.getMessage())); + } + } + + @Nested + class getUser_메서드는 { + @Test + void 유저_기본키가_주어지면_유저_응답_객체_HTTP_응답을_반환한다() throws Exception { + // given + Long userId = 1L; + UserRes userRes = UserRes.builder() + .id(userId) + .email("example@gmail.com") + .nickname("ex닉네임1") + .build(); + given(userService.get(userId)).willReturn(userRes); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/user") + .contentType(MediaType.APPLICATION_JSON) + .queryParam("userId", userId.toString())) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(userRes.getId())) + .andExpect(jsonPath("$.email").value(userRes.getEmail())) + .andExpect(jsonPath("$.nickname").value(userRes.getNickname())); + } + @Test + void 만약_없는_유저의_기본키가_주어지면_USER_NOT_FOUND_예외가_반환된다() throws Exception { + // given + Long nonExistingUserId = 404L; + given(userService.get(nonExistingUserId)).willThrow(new CustomException(ErrorCode.USER_NOT_FOUND)); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/user") + .contentType(MediaType.APPLICATION_JSON) + .queryParam("userId", nonExistingUserId.toString())) + // then + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ErrorCode.USER_NOT_FOUND.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.USER_NOT_FOUND.getMessage())); + } + } + + @Nested + class patchUser_메서드는 { + @Test + void 유저_기본키와_수정할_정보가_주어지면_유저_정보가_성공적으로_수정된다() throws Exception { + // given + Long userId = 1L; + String userPatchJson = """ + { + "password": "newExamplePWD123", + "nickname": "newEx닉네임1" + } + """; + Long modifiedUserId = 1L; + given(userService.modify(any(UserPatch.class), eq(userId))).willReturn(modifiedUserId); + + // when then + mockMvc.perform(MockMvcRequestBuilders + .patch("/api/user") + .contentType(MediaType.APPLICATION_JSON) + .content(userPatchJson) + .queryParam("userId", userId.toString())).andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").value(modifiedUserId)); + } + @Test + void 만약_존재하지_않는_유저_기본키로_수정을_시도하면_USER_NOT_FOUND_예외가_반환된다() throws Exception { + // given + Long nonExistingUserId = 404L; + String userPatchJson = """ + { + "password": "newExamplePWD123", + "nickname": "newEx닉네임1" + } + """; + given(userService.modify(any(UserPatch.class), eq(nonExistingUserId))).willThrow(new CustomException(ErrorCode.USER_NOT_FOUND)); + + // when then + mockMvc.perform(MockMvcRequestBuilders + .patch("/api/user") + .contentType(MediaType.APPLICATION_JSON) + .content(userPatchJson) + .queryParam("userId", nonExistingUserId.toString())) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(ErrorCode.USER_NOT_FOUND.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.USER_NOT_FOUND.getMessage())); + } + } + + @Nested + class deleteUser_메서드는 { + @Test + void 유저_기본키가_주어지면_유저가_성공적으로_삭제된다() throws Exception { + // given + Long userId = 1L; + willDoNothing().given(userService).delete(userId); + + // when then + mockMvc.perform(MockMvcRequestBuilders + .delete("/api/user") + .contentType(MediaType.APPLICATION_JSON) + .queryParam("userId", userId.toString())) + .andExpect(status().isNoContent()); + } + @Test + void 만약_존재하지_않는_유저를_삭제하려고_하면_유저_삭제_예외가_발생한다() throws Exception { + // given + Long userId = 404L; + CustomException expectedException = new CustomException(ErrorCode.USER_NOT_FOUND); + willThrow(expectedException).given(userService).delete(userId); + + // when then + mockMvc.perform(MockMvcRequestBuilders + .delete("/api/user") + .contentType(MediaType.APPLICATION_JSON) + .queryParam("userId", userId.toString())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(ErrorCode.USER_NOT_FOUND.toString())) + .andExpect(jsonPath("$.message").value(ErrorCode.USER_NOT_FOUND.getMessage())); + } + } +} \ No newline at end of file diff --git a/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/service/FolderServiceTest.java b/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/service/FolderServiceTest.java new file mode 100644 index 0000000..5f5bf61 --- /dev/null +++ b/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/service/FolderServiceTest.java @@ -0,0 +1,265 @@ +package com.serverstudy.todolist.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.serverstudy.todolist.domain.Folder; +import com.serverstudy.todolist.domain.Todo; +import com.serverstudy.todolist.dto.request.FolderReq.FolderPatch; +import com.serverstudy.todolist.dto.request.FolderReq.FolderPost; +import com.serverstudy.todolist.dto.response.FolderRes; +import com.serverstudy.todolist.exception.CustomException; +import com.serverstudy.todolist.exception.ErrorCode; +import com.serverstudy.todolist.repository.FolderRepository; +import com.serverstudy.todolist.repository.TodoRepository; +import com.serverstudy.todolist.repository.UserRepository; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class FolderServiceTest { + + @InjectMocks + FolderService folderService; + @Mock + FolderRepository folderRepository; + @Mock + UserRepository userRepository; + @Mock + TodoRepository todoRepository; + @Captor + private ArgumentCaptor folderCaptor; + ObjectMapper objectMapper = new ObjectMapper(); + + @Nested + class create_메서드는 { + FolderPost givenFolderPost() throws JsonProcessingException { + String folderPostJson = """ + { + "name": "과제 폴더" + } + """; + return objectMapper.readValue(folderPostJson, FolderPost.class); + } + + @Test + void 폴더_이름과_유저_기본키가_주어지면_새로운_폴더를_생성하고_기본키를_반환한다() throws JsonProcessingException { + // given + FolderPost folderPost = givenFolderPost(); + Long userId = 1L; + Folder folder = Folder.builder() + .name(folderPost.getName()) + .userId(userId) + .build(); + Long folderId = 1L; + ReflectionTestUtils.setField(folder, "id", folderId); + given(userRepository.existsById(userId)).willReturn(true); + given(folderRepository.existsByNameAndUserId(folderPost.getName(), userId)).willReturn(false); + given(folderRepository.save(any(Folder.class))).willReturn(folder); + + // when + long result = folderService.create(folderPost, userId); + + // then + assertThat(result).isEqualTo(folderId); + verify(userRepository, times(1)).existsById(userId); + verify(folderRepository, times(1)).existsByNameAndUserId(folderPost.getName(), userId); + verify(folderRepository, times(1)).save(folderCaptor.capture()); + Folder savedFolder = folderCaptor.getValue(); + assertThat(savedFolder.getName()).isEqualTo(folderPost.getName()); + assertThat(savedFolder.getUserId()).isEqualTo(userId); + } + + @Test + void 만약_존재하지_않는_유저의_기본키가_주어지면_USER_NOT_FOUND_예외를_던진다() throws JsonProcessingException { + // given + FolderPost folderPost = givenFolderPost(); + Long userId = 1L; + given(userRepository.existsById(userId)).willReturn(false); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> folderService.create(folderPost, userId)); + + // then + assertEquals(ErrorCode.USER_NOT_FOUND, exception.getErrorCode()); + verify(userRepository, times(1)).existsById(userId); + verify(folderRepository, times(0)).existsByNameAndUserId(any(String.class), any(Long.class)); + verify(folderRepository, times(0)).save(any(Folder.class)); + } + + @Test + void 만약_중복된_폴더_이름이_주어지면_DUPLICATE_FOLDER_NAME_예외를_던진다() throws JsonProcessingException { + // given + FolderPost folderPost = givenFolderPost(); + Long userId = 1L; + given(userRepository.existsById(userId)).willReturn(true); + given(folderRepository.existsByNameAndUserId(folderPost.getName(), userId)).willReturn(true); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> folderService.create(folderPost, userId)); + + // then + assertEquals(ErrorCode.DUPLICATE_FOLDER_NAME, exception.getErrorCode()); + verify(userRepository, times(1)).existsById(userId); + verify(folderRepository, times(1)).existsByNameAndUserId(folderPost.getName(), userId); + verify(folderRepository, times(0)).save(any(Folder.class)); + } + } + + @Nested + class getAllWithTodoCount_메서드는 { + @Test + void 유저_기본키가_주어지면_유저의_모든_폴더의_정보를_반환한다() { + // given + Long userId = 1L; + Folder folder1 = Folder.builder().name("Folder 1").userId(userId).build(); + ReflectionTestUtils.setField(folder1, "id", 1L); + Folder folder2 = Folder.builder().name("Folder 2").userId(userId).build(); + ReflectionTestUtils.setField(folder2, "id", 2L); + + List folderList = List.of(folder1, folder2); + + given(folderRepository.findAllByUserIdOrderByNameAsc(userId)).willReturn(folderList); + given(todoRepository.countByFolder(folder1)).willReturn(3); + given(todoRepository.countByFolder(folder2)).willReturn(5); + + // when + List result = folderService.getAllWithTodoCount(userId); + + // then + assertThat(result).hasSize(2); + assertThat(result.get(0).getFolderId()).isEqualTo(folder1.getId()); + assertThat(result.get(0).getName()).isEqualTo(folder1.getName()); + assertThat(result.get(0).getTodoCount()).isEqualTo(3); + assertThat(result.get(1).getFolderId()).isEqualTo(folder2.getId()); + assertThat(result.get(1).getName()).isEqualTo(folder2.getName()); + assertThat(result.get(1).getTodoCount()).isEqualTo(5); + } + } + + @Nested + class modify_메서드는 { + @Test + void 수정할_폴더_이름과_폴더_기본키가_주어지면_수정하고_폴더_기본키를_반환한다() throws JsonProcessingException { + // given + Long folderId = 1L; + Long userId = 1L; + String folderPatchJson = """ + { + "name": "New Folder Name" + } + """; + FolderPatch folderPatch = objectMapper.readValue(folderPatchJson, FolderPatch.class); + + Folder folder = Folder.builder().name("Old Folder Name").userId(userId).build(); + ReflectionTestUtils.setField(folder, "id", folderId); + + given(folderRepository.findById(folderId)).willReturn(Optional.of(folder)); + given(folderRepository.existsByNameAndUserId(folderPatch.getName(), userId)).willReturn(false); + + // when + long result = folderService.modify(folderPatch, folderId); + + // then + assertThat(result).isEqualTo(folderId); + assertThat(folder.getName()).isEqualTo(folderPatch.getName()); + verify(folderRepository, times(1)).findById(folderId); + verify(folderRepository, times(1)).existsByNameAndUserId(folderPatch.getName(), userId); + } + + @Test + void 만약_중복된_폴더_이름이_존재하면_DUPLICATE_FOLDER_NAME_예외를_던진다() throws JsonProcessingException { + // given + Long folderId = 1L; + Long userId = 1L; + String folderPatchJson = """ + { + "name": "New Folder Name" + } + """; + FolderPatch folderPatch = objectMapper.readValue(folderPatchJson, FolderPatch.class); + + Folder folder = Folder.builder().name("Old Folder Name").userId(userId).build(); + + given(folderRepository.findById(folderId)).willReturn(Optional.of(folder)); + given(folderRepository.existsByNameAndUserId(folderPatch.getName(), userId)).willReturn(true); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> folderService.modify(folderPatch, folderId)); + + // then + assertEquals(ErrorCode.DUPLICATE_FOLDER_NAME, exception.getErrorCode()); + verify(folderRepository, times(1)).findById(folderId); + verify(folderRepository, times(1)).existsByNameAndUserId(folderPatch.getName(), userId); + } + + @Test + void 만약_존재하지_않는_폴더의_기본키가_주어지면_FOLDER_NOT_FOUND_예외를_던진다() throws JsonProcessingException { + // given + Long folderId = 1L; + String folderPatchJson = """ + { + "name": "New Folder Name" + } + """; + FolderPatch folderPatch = objectMapper.readValue(folderPatchJson, FolderPatch.class); + + given(folderRepository.findById(folderId)).willReturn(Optional.empty()); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> folderService.modify(folderPatch, folderId)); + + // then + assertEquals(ErrorCode.FOLDER_NOT_FOUND, exception.getErrorCode()); + verify(folderRepository, times(1)).findById(folderId); + } + } + @Nested + class delete_메서드는 { + @Test + void 폴더_기본키가_주어지면_해당_폴더를_삭제하고_폴더에_속한_투두를_폴더없음으로_바꾼다() { + // given + Long folderId = 1L; + Folder folder = Folder.builder().name("Folder").userId(1L).build(); + + List todoList = List.of( + Todo.builder().title("Todo 1").folder(folder).build(), + Todo.builder().title("Todo 2").folder(folder).build() + ); + + given(folderRepository.findById(folderId)).willReturn(Optional.of(folder)); + given(todoRepository.findAllByFolder(any(Folder.class))).willReturn(todoList); + + // when + folderService.delete(folderId); + + // then + assertThat(todoList.get(0).getFolder()).isEqualTo(null); + assertThat(todoList.get(1).getFolder()).isEqualTo(null); + verify(folderRepository, times(1)).findById(folderId); + verify(todoRepository, times(1)).findAllByFolder(folder); + verify(folderRepository, times(1)).delete(folder); + } + } +} \ No newline at end of file diff --git a/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/service/TodoServiceTest.java b/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/service/TodoServiceTest.java new file mode 100644 index 0000000..50fabb5 --- /dev/null +++ b/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/service/TodoServiceTest.java @@ -0,0 +1,390 @@ +package com.serverstudy.todolist.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.serverstudy.todolist.domain.Folder; +import com.serverstudy.todolist.domain.Todo; +import com.serverstudy.todolist.domain.enums.Priority; +import com.serverstudy.todolist.domain.enums.Progress; +import com.serverstudy.todolist.dto.response.TodoRes; +import com.serverstudy.todolist.exception.CustomException; +import com.serverstudy.todolist.exception.ErrorCode; +import com.serverstudy.todolist.repository.FolderRepository; +import com.serverstudy.todolist.repository.TodoRepository; +import com.serverstudy.todolist.repository.UserRepository; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static com.serverstudy.todolist.dto.request.TodoReq.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class TodoServiceTest { + @InjectMocks + TodoService todoService; + @Mock + TodoRepository todoRepository; + @Mock + UserRepository userRepository; + @Mock + FolderRepository folderRepository; + @Captor + private ArgumentCaptor todoCaptor; + ObjectMapper objectMapper = new ObjectMapper(); + + @Nested + class create_메서드는 { + TodoPost givenTodoPost() throws JsonProcessingException { + String todoPostJson = """ + { + "title": "컴퓨터공학개론 레포트", + "description": "주제: 컴퓨터공학이란 무엇인가?, 분량: 3장 이상", + "priority": "NONE", + "progress": "TODO", + "folderId": 1 + } + """; + return objectMapper.readValue(todoPostJson, TodoPost.class); + } + + @Test + void 주어진_투두_정보와_유저_기본키에_대해_투두를_생성_및_저장하고_기본키를_반환한다() throws JsonProcessingException { + // given + Long userId = 1L; + Long folderId = 1L; + TodoPost todoPost = givenTodoPost(); + + Folder folder = Folder.builder().name("Folder").userId(userId).build(); + ReflectionTestUtils.setField(folder, "id", 1L); + + Todo todo = todoPost.toEntity(userId, folder); + ReflectionTestUtils.setField(todo, "id", 1L); + + given(userRepository.existsById(userId)).willReturn(true); + given(folderRepository.findById(folderId)).willReturn(Optional.of(folder)); + given(todoRepository.save(any(Todo.class))).willReturn(todo); + + // when + long result = todoService.create(todoPost, userId); + + // then + assertThat(result).isEqualTo(1L); + verify(userRepository, times(1)).existsById(userId); + verify(folderRepository, times(1)).findById(folderId); + verify(todoRepository, times(1)).save(todoCaptor.capture()); + Todo savedTodo = todoCaptor.getValue(); + assertThat(savedTodo.getTitle()).isEqualTo(todoPost.getTitle()); + assertThat(savedTodo.getDescription()).isEqualTo(todoPost.getDescription()); + assertThat(savedTodo.getFolder().getId()).isEqualTo(folderId); + } + @Test + void 만약_존재하지_않는_유저_기본키가_주어지면_USER_NOT_FOUND_예외를_던진다() throws JsonProcessingException { + // given + Long userId = 1L; + TodoPost todoPost = givenTodoPost(); + given(userRepository.existsById(userId)).willReturn(false); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> todoService.create(todoPost, userId)); + + // then + assertEquals(ErrorCode.USER_NOT_FOUND, exception.getErrorCode()); + verify(userRepository, times(1)).existsById(userId); + verify(todoRepository, times(0)).save(any(Todo.class)); + } + } + + @Nested + class findAllByConditions_메서드는 { + @Test + void 조건에_맞는_투두들을_검색한_후_투두_응답_객체_리스트로_반환한다() throws JsonProcessingException { + // given + Long userId = 1L; + String todoGetJson = """ + { + "priority": "NONE", + "progress": "TODO", + "isDeleted": false + } + """; + TodoGet todoGet = objectMapper.readValue(todoGetJson, TodoGet.class); + List todoList = List.of( + Todo.builder().title("Todo 1").priority(Priority.NONE).progress(Progress.TODO).userId(userId).build(), + Todo.builder().title("Todo 2").priority(Priority.NONE).progress(Progress.TODO).userId(userId).build() + ); + ReflectionTestUtils.setField(todoList.get(0),"id", 1L); + todoList.get(0).moveToTrash(); + ReflectionTestUtils.setField(todoList.get(1),"id", 2L); + + given(todoRepository.findAllByConditions(any(), eq(userId), any(Priority.class), any(Progress.class), any(Boolean.class))).willReturn(todoList); + + // when + List result = todoService.findAllByConditions(todoGet, userId); + + // then + assertThat(result).hasSize(2); + assertThat(result.get(0).getId()).isEqualTo(1L); + assertThat(result.get(0).getTitle()).isEqualTo(todoList.get(0).getTitle()); + assertThat(result.get(0).getDateFromDelete()).isEqualTo(0); + assertThat(result.get(1).getId()).isEqualTo(2L); + assertThat(result.get(1).getTitle()).isEqualTo(todoList.get(1).getTitle()); + } + } + + @Nested + class update_메서드는 { + @Test + void 주어진_투두_정보와_유저_기본키에_대해_투두_정보를_수정하고_기본키를_반환한다() throws JsonProcessingException { + // given + Long todoId = 1L; + String todoPutJson = """ + { + "title": "앱센터 발표", + "description": "주제: 스프링이란?, 비고: GitHub에 업로드 ", + "priority": "PRIMARY", + "progress": "DOING", + "folderId": 1 + } + """; + TodoPut todoPut = objectMapper.readValue(todoPutJson, TodoPut.class); + + Todo todo = Todo.builder().title("Todo").build(); + ReflectionTestUtils.setField(todo, "id", todoId); + Folder folder = Folder.builder().name("Folder").build(); + + given(todoRepository.findById(todoId)).willReturn(Optional.of(todo)); + given(folderRepository.findById(todoPut.getFolderId())).willReturn(Optional.of(folder)); + + // when + long result = todoService.update(todoPut, todoId); + + // then + assertThat(result).isEqualTo(todoId); + verify(todoRepository, times(1)).findById(todoId); + verify(folderRepository, times(1)).findById(todoPut.getFolderId()); + } + @Test + void 존재하지_않는_투두_기본키가_주어지면_TODO_NOT_FOUND_예외를_던진다() throws JsonProcessingException { + // given + Long todoId = 1L; + String todoPutJson = """ + { + "title": "앱센터 발표", + "description": "주제: 스프링이란?, 비고: GitHub에 업로드 ", + "priority": "PRIMARY", + "progress": "DOING" + } + """; + TodoPut todoPut = objectMapper.readValue(todoPutJson, TodoPut.class); + given(todoRepository.findById(todoId)).willReturn(Optional.empty()); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> todoService.update(todoPut, todoId)); + + // then + assertEquals(ErrorCode.TODO_NOT_FOUND, exception.getErrorCode()); + verify(todoRepository, times(1)).findById(todoId); + verify(folderRepository, times(0)).findById(todoPut.getFolderId()); + } + } + + @Nested + class switchProgress_메서드는 { + @Test + void 주어진_투두_기본키에_해당하는_투두_상태를_변경하고_기본키를_반환한다() { + // given + Long todoId = 1L; + Todo todo = Todo.builder().title("Todo").progress(Progress.TODO).build(); + ReflectionTestUtils.setField(todo, "id", todoId); + given(todoRepository.findById(todoId)).willReturn(Optional.of(todo)); + Todo switchedTodo =Todo.builder().title("Todo").progress(Progress.TODO).build(); + switchedTodo.switchProgress(); + + // when + long result = todoService.switchProgress(todoId); + + // then + assertThat(result).isEqualTo(todoId); + assertThat(todo.getProgress()).isEqualTo(switchedTodo.getProgress()); + verify(todoRepository, times(1)).findById(todoId); + } + @Test + void 만약_주어진_투두_기본키에_해당하는_투두가_존재하지_않으면_TODO_NOT_FOUND_예외를_던진다() { + // given + Long todoId = 1L; + given(todoRepository.findById(todoId)).willReturn(Optional.empty()); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> todoService.switchProgress(todoId)); + + // then + assertEquals(ErrorCode.TODO_NOT_FOUND, exception.getErrorCode()); + verify(todoRepository, times(1)).findById(todoId); + } + } + + @Nested + class moveFolder_메서드는 { + @Test + void 투두를_해당_폴더로_이동하고_기본키를_반환한다() { + // given + Long folderId = 1L; + Long todoId = 1L; + Folder folder = Folder.builder().name("Folder").build(); + ReflectionTestUtils.setField(folder, "id", folderId); + Todo todo = Todo.builder().title("Todo").build(); + ReflectionTestUtils.setField(todo, "id", todoId); + + given(todoRepository.findById(todoId)).willReturn(Optional.of(todo)); + given(folderRepository.findById(folderId)).willReturn(Optional.of(folder)); + + // when + long result = todoService.moveFolder(folderId, todoId); + + // then + assertThat(result).isEqualTo(todoId); + assertThat(todo.getFolder()).isEqualTo(folder); + verify(todoRepository, times(1)).findById(todoId); + verify(folderRepository, times(1)).findById(folderId); + } + @Test + void 만약_투두_기본키에_해당하는_투두가_존재하지_않으면_TODO_NOT_FOUND_예외를_던진다() { + // given + Long folderId = 1L; + Long todoId = 1L; + given(todoRepository.findById(todoId)).willReturn(Optional.empty()); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> todoService.moveFolder(folderId, todoId)); + + // then + assertEquals(ErrorCode.TODO_NOT_FOUND, exception.getErrorCode()); + verify(todoRepository, times(1)).findById(todoId); + verify(folderRepository, times(0)).findById(folderId); + } + @Test + void 만약_폴더_기본키에_해당하는_폴더가_존재하지_않으면_FOLDER_NOT_FOUND_예외를_던진다() { + // given + Long folderId = 1L; + Long todoId = 1L; + Todo todo = Todo.builder().title("Todo").build(); + ReflectionTestUtils.setField(todo, "id", todoId); + given(todoRepository.findById(todoId)).willReturn(Optional.of(todo)); + given(folderRepository.findById(folderId)).willReturn(Optional.empty()); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> todoService.moveFolder(folderId, todoId)); + + // then + assertEquals(ErrorCode.FOLDER_NOT_FOUND, exception.getErrorCode()); + verify(todoRepository, times(1)).findById(todoId); + verify(folderRepository, times(1)).findById(folderId); + } + } + + @Nested + class delete_메서드는 { + @Test + void restore_값이_false_이면_투두를_삭제하고_null_을_반환한다() { + // given + Long todoId = 1L; + Boolean restore = false; + Todo todo = Todo.builder().title("Todo").build(); + + given(todoRepository.findById(todoId)).willReturn(Optional.of(todo)); + + // when + Long result = todoService.delete(todoId, restore); + + // then + assertThat(result).isNull(); + verify(todoRepository, times(1)).findById(todoId); + verify(todoRepository, times(1)).delete(todo); + } + + @Test + void restore_값이_true_이면_투두를_휴지통으로_옮기고_기본키를_반환한다() { + // given + Long todoId = 1L; + Boolean restore = true; + Todo todo = Todo.builder().title("Todo").build(); + + given(todoRepository.findById(todoId)).willReturn(Optional.of(todo)); + + // when + Long result = todoService.delete(todoId, restore); + + // then + assertThat(result).isEqualTo(todoId); + verify(todoRepository, times(1)).findById(todoId); + verify(todoRepository, times(0)).delete(todo); + assertThat(todo.getIsDeleted()).isTrue(); + } + @Test + void 투두_기본키가_존재하지_않으면_TODO_NOT_FOUND_예외를_던진다() { + // given + Long todoId = 1L; + Boolean restore = false; + given(todoRepository.findById(todoId)).willReturn(Optional.empty()); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> todoService.delete(todoId, restore)); + + // then + assertEquals(ErrorCode.TODO_NOT_FOUND, exception.getErrorCode()); + verify(todoRepository, times(1)).findById(todoId); + } + } + + @Nested + class deleteInTrash_메서드는 { + @Test + void 휴지통에_있는_30일_지난_투두들을_삭제한다() { + // given + Todo todo1 = Todo.builder().title("Todo1").build(); + ReflectionTestUtils.setField(todo1, "id", 1L); + todo1.moveToTrash(); + ReflectionTestUtils.setField(todo1, "deletedTime", LocalDateTime.now().minusDays(31)); + + Todo todo2 = Todo.builder().title("Todo2").build(); + ReflectionTestUtils.setField(todo2, "id", 2L); + todo2.moveToTrash(); + ReflectionTestUtils.setField(todo2, "deletedTime", LocalDateTime.now().minusDays(10)); + + List todoList = List.of(todo1, todo2); + given(todoRepository.findAllByIsDeletedOrderByDeletedTimeAsc(true)).willReturn(todoList); + + // when + todoService.deleteInTrash(); + + // then + verify(todoRepository, times(1)).findAllByIsDeletedOrderByDeletedTimeAsc(true); + verify(todoRepository, times(1)).delete(todo1); + verify(todoRepository, times(0)).delete(todo2); + } + } +} \ No newline at end of file diff --git a/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/service/UserServiceTest.java b/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/service/UserServiceTest.java new file mode 100644 index 0000000..93f0e44 --- /dev/null +++ b/contents/todoListAPI/seungseop/todolist/src/test/java/com/serverstudy/todolist/service/UserServiceTest.java @@ -0,0 +1,289 @@ +package com.serverstudy.todolist.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.serverstudy.todolist.domain.Folder; +import com.serverstudy.todolist.domain.Todo; +import com.serverstudy.todolist.domain.User; +import com.serverstudy.todolist.dto.request.UserReq.UserPatch; +import com.serverstudy.todolist.dto.response.UserRes; +import com.serverstudy.todolist.exception.CustomException; +import com.serverstudy.todolist.exception.ErrorCode; +import com.serverstudy.todolist.repository.FolderRepository; +import com.serverstudy.todolist.repository.TodoRepository; +import com.serverstudy.todolist.repository.UserRepository; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static com.serverstudy.todolist.dto.request.UserReq.UserPost; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class UserServiceTest { + + @InjectMocks + UserService userService; + @Mock + UserRepository userRepository; + @Mock + TodoRepository todoRepository; + @Mock + FolderRepository folderRepository; + @Captor + private ArgumentCaptor userCaptor; + ObjectMapper objectMapper = new ObjectMapper(); + + @Nested + class join_메서드는 { + UserPost givenUserPost() throws JsonProcessingException { + String userPostJson = """ + { + "email": "example@gmail.com", + "password": "examplePWD123", + "nickname": "ex닉네임1" + } + """; + return objectMapper.readValue(userPostJson, UserPost.class); + } + + @Test + void 이메일_비밀번호_닉네임이_주어지면_새로운_유저를_생성하고_기본키를_반환한다() throws JsonProcessingException { + // given + /*인자 생성*/ + UserPost userPost = givenUserPost(); + /*호출 반환 값 생성*/ + User user = User.builder() + .email(userPost.getEmail()) + .password(userPost.getPassword()) + .nickname(userPost.getNickname()) + .build(); + ReflectionTestUtils.setField(user, "id", 1L); + /*Mocking*/ + given(userRepository.existsByEmail(userPost.getEmail())).willReturn(false); + given(userRepository.save(any(User.class))).willReturn(user); + + // when + long result = userService.join(userPost); + + // then + /*반환 값 검증*/ + assertThat(result).isEqualTo(1L); + /*호출 횟수 검증*/ + verify(userRepository, times(1)).existsByEmail(userPost.getEmail()); + verify(userRepository, times(1)).save(any(User.class)); + /*호출 인자 검증*/ + verify(userRepository).save(userCaptor.capture()); + User savedUser = userCaptor.getValue(); + assertThat(savedUser.getEmail()).isEqualTo(userPost.getEmail()); + assertThat(savedUser.getPassword()).isEqualTo(userPost.getPassword()); + assertThat(savedUser.getNickname()).isEqualTo(userPost.getNickname()); + } + @Test + void 만약_중복된_이메일이_주어지면_DUPLICATE_USER_EMAIL_예외를_던진다() throws JsonProcessingException { + // given + /*인자 생성*/ + UserPost userPost = givenUserPost(); + /*Mocking*/ + given(userRepository.existsByEmail(userPost.getEmail())).willReturn(true); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> userService.join(userPost)); + + // then + assertEquals(ErrorCode.DUPLICATE_USER_EMAIL, exception.getErrorCode()); + /*호출 횟수 검증*/ + verify(userRepository, times(1)).existsByEmail(userPost.getEmail()); + verify(userRepository, times(0)).save(any(User.class)); + } + + } + + @Nested + class checkEmailDuplicated_메서드는 { + @Test + void 이메일이_주어지면_아무것도_반환하지_않는다() { + // given + String email = "notDuplicated@email.com"; + given(userRepository.existsByEmail(email)).willReturn(false); + + // when + userService.checkEmailDuplicated(email); + + // then + verify(userRepository, times(1)).existsByEmail(email); + } + @Test + void 만약_중복된_이메일이_주어지면_DUPLICATE_USER_EMAIL_예외를_던진다() { + // given + String email = "duplicated@email.com"; + given(userRepository.existsByEmail(email)).willReturn(true); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> userService.checkEmailDuplicated(email)); + + // then + assertEquals(ErrorCode.DUPLICATE_USER_EMAIL, exception.getErrorCode()); + verify(userRepository, times(1)).existsByEmail(email); + } + } + + @Nested + class get_메서드는 { + @Test + void 유저_기본키가_주어지면_유저_응답_객체를_반환한다() { + // given + Long userId = 1L; + User user = User.builder() + .email("test@email.com") + .nickname("nickname") + .password("password") + .build(); + ReflectionTestUtils.setField(user, "id", 1L); + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + + // when + UserRes result = userService.get(userId); + + // then + assertThat(result.getId()).isEqualTo(1L); + assertThat(result.getEmail()).isEqualTo(user.getEmail()); + assertThat(result.getNickname()).isEqualTo(user.getNickname()); + verify(userRepository, times(1)).findById(userId); + } + @Test + void 만약_없는_유저의_기본키가_주어지면_USER_NOT_FOUND_예외를_던진다() { + // given + Long userId = 1L; + given(userRepository.findById(userId)).willReturn(Optional.empty()); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> userService.get(userId)); + + // then + assertEquals(ErrorCode.USER_NOT_FOUND, exception.getErrorCode()); + verify(userRepository, times(1)).findById(userId); + } + } + + @Nested + class modify_메서드는 { + private UserPatch givenUserPatch() throws JsonProcessingException { + String userPatchJson = """ + { + "password": "newExamplePWD123", + "nickname": "newEx닉네임1" + } + """; + return objectMapper.readValue(userPatchJson, UserPatch.class); + } + + @Test + void 비밀번호_닉네임과_유저_기본키가_주어지면_해당_정보를_수정하고_기본키를_반환한다() throws JsonProcessingException { + // given + UserPatch userPatch = givenUserPatch(); + Long userId = 1L; + User user = User.builder() + .email("test@email.com") + .nickname("nickname") + .password("password") + .build(); + ReflectionTestUtils.setField(user, "id", 1L); + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + + // when + long result = userService.modify(userPatch, userId); + + // then + assertThat(result).isEqualTo(userId); + assertThat(user.getPassword()).isEqualTo(userPatch.getPassword()); + assertThat(user.getNickname()).isEqualTo(userPatch.getNickname()); + verify(userRepository, times(1)).findById(userId); + } + @Test + void 만약_없는_유저의_기본키가_주어지면_USER_NOT_FOUND_예외를_던진다() throws JsonProcessingException { + // given + UserPatch userPatch = givenUserPatch(); + Long userId = 1L; + given(userRepository.findById(userId)).willReturn(Optional.empty()); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> userService.modify(userPatch, userId)); + + // then + assertEquals(ErrorCode.USER_NOT_FOUND, exception.getErrorCode()); + verify(userRepository, times(1)).findById(userId); + } + } + + @Nested + class delete_메서드는 { + @Test + void 유저_기본키가_주어지면_해당_유저와_관련된_모든_정보를_삭제한다() { + // given + Long userId = 1L; + User user = User.builder() + .email("test@email.com") + .nickname("nickname") + .password("password") + .build(); + ReflectionTestUtils.setField(user, "id", userId); + List todoList = Collections.emptyList(); + List folderList = Collections.emptyList(); + + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + given(todoRepository.findAllByUserId(userId)).willReturn(todoList); + given(folderRepository.findAllByUserId(userId)).willReturn(folderList); + + // when + userService.delete(userId); + + // then + verify(userRepository, times(1)).findById(userId); + verify(todoRepository, times(1)).findAllByUserId(userId); + verify(todoRepository, times(1)).deleteAll(todoList); + verify(folderRepository, times(1)).findAllByUserId(userId); + verify(folderRepository, times(1)).deleteAll(folderList); + verify(userRepository, times(1)).delete(user); + } + @Test + void 만약_없는_유저의_기본키가_주어지면_USER_NOT_FOUND_예외를_던진다() { + // given + Long userId = 1L; + given(userRepository.findById(userId)).willReturn(Optional.empty()); + + // when + CustomException exception = assertThrows(CustomException.class, + () -> userService.delete(userId)); + + // then + assertEquals(ErrorCode.USER_NOT_FOUND, exception.getErrorCode()); + verify(userRepository, times(1)).findById(userId); + verify(todoRepository, times(0)).findAllByUserId(userId); + verify(todoRepository, times(0)).deleteAll(any()); + verify(folderRepository, times(0)).findAllByUserId(userId); + verify(folderRepository, times(0)).deleteAll(any()); + verify(userRepository, times(0)).delete(any(User.class)); + } + } +} \ No newline at end of file