th42500의 TIL

[JUnit] @Nested란? 본문

Backend/JUnit & BDD

[JUnit] @Nested란?

th42500 2023. 1. 4. 15:25

프로젝트 발표 시간에 다른 교육생들의 발표를 듣다가 @Nested라는 Annotation을 처음 알게되어 직접 적용해보고 블로그 글을 작성하게 되었다.

 

@Nested Test 란?

  • 테스트 작성자에게 여러 테스트 그룹 간의 관계를 표현할 수 있도록 해주는 Annotation
  • import org.junit.jupiter.api에서 제공하는 기능
  • 외부 테스트의 설정 코드가 내부 테스트가 실행되기 전에 실행되므로 모든 테스트를 독립적으로 실행할 수 있음

⚠ @BeforeAll 또는 @AfterAll 를 제외한 중첩 클래스 (내부 클래스)만 @Nested 테스트 클래스로 사용 가능

👉 Java16 이전 버전에서는 내부 클래스에서 static을 허용하지 않기 때문

👉 @TestInstance(Lifecycle.PER_CLASS) 를 이용하여 @Nested 테스트 클래스에 주석을 달면 해결 가능

 

 

@Nested 적용

  • 관심사가 비슷한 메소드들을 @Nested 로 묶음
  • 전체적인 코드의 양은 늘어나지만 계층적인 구조로 이루어져 있어 가독성이 높아짐
  • 클래스로 구분되어 있어 success 또는 fail 등의 각 기능별 내부 테스트 코드 클래스명이 같아도 문제가 없음

 

기존 코드 - @Nested 적용 전

package com.likelion.healing.controller;

// import 생략

@WebMvcTest(PostController.class)
@WithMockUser
class PostControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    PostService postService;

    @Autowired
    ObjectMapper objectMapper;

    @Test
    @DisplayName("포스트 작성 성공")
    void successfulCreatePost() throws Exception {
        PostReq req = PostReq.builder()
                .title("title1")
                .body("body1")
                .build();

        given(postService.createPost(any(PostReq.class), "Bearer " + any(String.class))).willReturn(new PostRes("포스트 등록 완료", 1));

        mockMvc.perform(post("/api/v1/posts")
                .with(csrf())
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsBytes(req)))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                .andExpect(jsonPath("$.result.message").value("포스트 등록 완료"))
                .andExpect(jsonPath("$.result.postId").value(1));
    }

    @Test
    @WithAnonymousUser
    @DisplayName("포스트 작성 실패 - JWT를 Bearer Token으로 보내지 않은 경우")
    void notStartsWithBearer() throws Exception {
        PostReq req = PostReq.builder()
                .title("title1")
                .body("body1")
                .build();

        given(postService.createPost(any(PostReq.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.INVALID_PERMISSION, "사용자가 권한이 없습니다."));

        mockMvc.perform(post("/api/v1/posts")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(req)))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithAnonymousUser
    @DisplayName("포스트 작성 실패 - JWT가 유효하지 않은 경우")
    void expiredToken() throws Exception {
        PostReq req = PostReq.builder()
                .title("title1")
                .body("body1")
                .build();

        given(postService.createPost(any(PostReq.class), "Bearer " + any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.INVALID_PERMISSION, "사용자가 권한이 없습니다."));

        mockMvc.perform(post("/api/v1/posts")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(req)))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

    @Test
    @DisplayName("포스트 전체 목록 조회 - 생성일자 내림차순")
    void getPostList() throws Exception {
        List<PostViewRes> postList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            LocalDateTime now = LocalDateTime.now().plusMinutes(i);
            postList.add(new PostViewRes(i, "title"+i, "body"+i, "test", now, now));
        }
        Page<PostViewRes> postViewResPage = new PageImpl<>(postList);
        postViewResPage.stream().sorted(Comparator.comparing(PostViewRes::getCreatedAt).reversed());

        given(postService.getPostList(any(Pageable.class))).willReturn(postViewResPage);

        mockMvc.perform(get("/api/v1/posts")
                        .param("page", "0")
                        .param("size", "10")
                        .param("sort", "createdAt,desc"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.resultCode").value("SUCCESS"));

    }

    @Test
    @DisplayName("포스트 단건 조회 성공")
    void successfulGetPostById() throws Exception {
        Integer postId = 1;
        PostViewRes post = PostViewRes.builder()
                .id(1)
                .title("title1")
                .body("body1")
                .userName("Soyeong")
                .createdAt(LocalDateTime.now())
                .lastModifiedAt(LocalDateTime.now())
                .build();

        given(postService.getPostById(any(Integer.class))).willReturn(post);

        mockMvc.perform(get(String.format("/api/v1/posts/%d", postId))
                        .with(csrf()))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                .andExpect(jsonPath("$.result.id").value(1))
                .andExpect(jsonPath("$.result.title").value("title1"))
                .andExpect(jsonPath("$.result.body").value("body1"))
                .andExpect(jsonPath("$.result.userName").value("Soyeong"))
                .andExpect(jsonPath("$.result.createdAt").exists())
                .andExpect(jsonPath("$.result.lastModifiedAt").exists());
    }

    @Test
    @WithAnonymousUser
    @DisplayName("포스트 수정 실패 - 인증 실패")
    void update_authenticationFailed() throws Exception {
        PostReq req = PostReq.builder()
                .title("test title")
                .body("test body")
                .build();
        UserEntity user = UserEntity.builder()
                .userName("Soyeong")
                .password("12345")
                .build();
        Integer postId = 1;

        given(postService.updatePostById(any(Integer.class), any(PostReq.class), any(String.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.USERNAME_NOT_FOUND, String.format("%s은(는) 없는 회원입니다.", user.getUsername())));

        mockMvc.perform(put(String.format("/api/v1/posts/%d", postId))
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(req)))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

    @Test
    @DisplayName("포스트 수정 실패 - 작성자 불일치")
    void update_mismatchedAuthorAndUser() throws Exception {
        PostReq req = PostReq.builder()
                .title("test title")
                .body("test body")
                .build();
        Integer postId = 1;

        given(postService.updatePostById(any(Integer.class), any(PostReq.class), any(String.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.INVALID_PERMISSION, "사용자가 권한이 없습니다."));

        mockMvc.perform(put(String.format("/api/v1/posts/%d", postId))
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(req)))
                .andDo(print())
                .andExpect(status().isUnauthorized())
                .andExpect(jsonPath("$.resultCode").value("ERROR"))
                .andExpect(jsonPath("$.result.errorCode").value("INVALID_PERMISSION"))
                .andExpect(jsonPath("$.result.message").value("사용자가 권한이 없습니다."));
    }

    @Test
    @DisplayName("포스트 수정 실패 - 데이터베이스 에러")
    void update_notFoundDatabase() throws Exception {
        PostReq req = PostReq.builder()
                .title("test title")
                .body("test body")
                .build();
        Integer postId = 1;

        given(postService.updatePostById(any(Integer.class), any(PostReq.class), any(String.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.DATABASE_ERROR, "DB에러"));

        mockMvc.perform(put(String.format("/api/v1/posts/%d", postId))
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(req)))
                .andDo(print())
                .andExpect(status().isInternalServerError())
                .andExpect(jsonPath("$.resultCode").value("ERROR"))
                .andExpect(jsonPath("$.result.errorCode").value("DATABASE_ERROR"))
                .andExpect(jsonPath("$.result.message").value("DB에러"));
    }

    @Test
    @DisplayName("포스트 수정 성공")
    void successfulEdit() throws Exception {
        PostReq req = PostReq.builder()
                .title("test title")
                .body("test body")
                .build();
        Integer postId = 1;

        given(postService.updatePostById(any(Integer.class), any(PostReq.class), any(String.class), any(String.class))).willReturn(new PostRes("포스트 수정 완료", postId));

        mockMvc.perform(put(String.format("/api/v1/posts/%d", postId))
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(req)))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                .andExpect(jsonPath("$.result.message").value("포스트 수정 완료"))
                .andExpect(jsonPath("$.result.postId").value(postId));
    }

    @Test
    @DisplayName("포스트 삭제 성공")
    void successfulDelete() throws Exception {
        Integer postId = 1;

        given(postService.deletePostById(any(Integer.class), any(String.class), any(String.class))).willReturn(new PostRes("포스트 삭제 완료", postId));

        mockMvc.perform(delete(String.format("/api/v1/posts/%d", postId))
                        .with(csrf()))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                .andExpect(jsonPath("$.result.message").value("포스트 삭제 완료"))
                .andExpect(jsonPath("$.result.postId").value(postId));
    }

    @Test
    @WithAnonymousUser
    @DisplayName("포스트 삭제 실패 - 인증 실패")
    void delete_authenticationFailed() throws Exception {
        UserEntity user = UserEntity.builder()
                .userName("Soyeong")
                .password("12345")
                .build();
        Integer postId = 1;

        given(postService.deletePostById(any(Integer.class), any(String.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.USERNAME_NOT_FOUND, String.format("%s은(는) 없는 회원입니다.", user.getUsername())));

        mockMvc.perform(delete(String.format("/api/v1/posts/%d", postId))
                        .with(csrf()))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

    @Test
    @DisplayName("포스트 삭제 실패 - 작성자 불일치")
    void delete_mismatchedAuthorAndUser() throws Exception {
        Integer postId = 1;

        given(postService.deletePostById(any(Integer.class), any(String.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.INVALID_PERMISSION, "사용자가 권한이 없습니다."));

        mockMvc.perform(delete(String.format("/api/v1/posts/%d", postId))
                        .with(csrf()))
                .andDo(print())
                .andExpect(status().isUnauthorized())
                .andExpect(jsonPath("$.resultCode").value("ERROR"))
                .andExpect(jsonPath("$.result.errorCode").value("INVALID_PERMISSION"))
                .andExpect(jsonPath("$.result.message").value("사용자가 권한이 없습니다."));
    }

    @Test
    @DisplayName("포스트 삭제 실패 - 데이터베이스 에러")
    void delete_notFoundDatabase() throws Exception {
        Integer postId = 1;

        given(postService.deletePostById(any(Integer.class), any(String.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.DATABASE_ERROR, "DB에러"));

        mockMvc.perform(delete(String.format("/api/v1/posts/%d", postId))
                        .with(csrf()))
                .andDo(print())
                .andExpect(status().isInternalServerError())
                .andExpect(jsonPath("$.resultCode").value("ERROR"))
                .andExpect(jsonPath("$.result.errorCode").value("DATABASE_ERROR"))
                .andExpect(jsonPath("$.result.message").value("DB에러"));
    }

    @Test
    @DisplayName("마이피드 조회 성공")
    void successfulGetMyFeed() throws Exception {
        given(postService.getMyFeed(any(Pageable.class), any(String.class))).willReturn(Page.empty());

        mockMvc.perform(get("/api/v1/posts/my")
                        .param("page", "0")
                        .param("size", "10")
                        .param("sort", "createdAt,desc")
                        .param("userName", "user"))
                .andDo(print())
                .andExpect(status().isOk());
    }

    @Test
    @WithAnonymousUser
    @DisplayName("마이피드 조회 실패 - 로그인 하지 않은 경우")
    void NotLogin() throws Exception {
        given(postService.getMyFeed(any(Pageable.class), any(String.class))).willReturn(Page.empty());

        mockMvc.perform(get("/api/v1/posts/my")
                        .param("page", "0")
                        .param("size", "10")
                        .param("sort", "createdAt,desc")
                        .param("userName", "user"))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

}

@Nested 적용 전

모든 테스트가 한번에 실행되었을 때 가독성이 좋지 않음

 

 

리팩토링 후 코드 - @Nested 적용 후

package com.likelion.healing.controller;

// import 생략

@WebMvcTest(PostController.class)
@WithMockUser
class PostControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    PostService postService;

    @Autowired
    ObjectMapper objectMapper;

    @Nested
    @DisplayName("포스트 작성 테스트")
    class CreatePostTest{

        @Test
        @DisplayName("포스트 작성 성공")
        void successCreatePost() throws Exception {
            PostReq req = PostReq.builder()
                    .title("title1")
                    .body("body1")
                    .build();

            given(postService.createPost(any(PostReq.class), "Bearer " + any(String.class))).willReturn(new PostRes("포스트 등록 완료", 1));

            mockMvc.perform(post("/api/v1/posts")
                    .with(csrf())
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsBytes(req)))
                    .andDo(print())
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                    .andExpect(jsonPath("$.result.message").value("포스트 등록 완료"))
                    .andExpect(jsonPath("$.result.postId").value(1));
        }

        @Test
        @WithAnonymousUser
        @DisplayName("포스트 작성 실패 - JWT를 Bearer Token으로 보내지 않은 경우")
        void notStartsWithBearer() throws Exception {
            PostReq req = PostReq.builder()
                    .title("title1")
                    .body("body1")
                    .build();

            given(postService.createPost(any(PostReq.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.INVALID_PERMISSION, "사용자가 권한이 없습니다."));

            mockMvc.perform(post("/api/v1/posts")
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsBytes(req)))
                    .andDo(print())
                    .andExpect(status().isUnauthorized());
        }

        @Test
        @WithAnonymousUser
        @DisplayName("포스트 작성 실패 - JWT가 유효하지 않은 경우")
        void expiredToken() throws Exception {
            PostReq req = PostReq.builder()
                    .title("title1")
                    .body("body1")
                    .build();

            given(postService.createPost(any(PostReq.class), "Bearer " + any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.INVALID_PERMISSION, "사용자가 권한이 없습니다."));

            mockMvc.perform(post("/api/v1/posts")
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsBytes(req)))
                    .andDo(print())
                    .andExpect(status().isUnauthorized());
        }
    }

    @Test
    @DisplayName("포스트 전체 목록 조회 - 생성일자 내림차순")
    void getPostList() throws Exception {
        List<PostViewRes> postList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            LocalDateTime now = LocalDateTime.now().plusMinutes(i);
            postList.add(new PostViewRes(i, "title"+i, "body"+i, "test", now, now));
        }
        Page<PostViewRes> postViewResPage = new PageImpl<>(postList);
        postViewResPage.stream().sorted(Comparator.comparing(PostViewRes::getCreatedAt).reversed());

        given(postService.getPostList(any(Pageable.class))).willReturn(postViewResPage);

        mockMvc.perform(get("/api/v1/posts")
                        .param("page", "0")
                        .param("size", "10")
                        .param("sort", "createdAt,desc"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.resultCode").value("SUCCESS"));

    }

    @Test
    @DisplayName("포스트 단건 조회 성공")
    void successGetPostById() throws Exception {
        Integer postId = 1;
        PostViewRes post = PostViewRes.builder()
                .id(1)
                .title("title1")
                .body("body1")
                .userName("Soyeong")
                .createdAt(LocalDateTime.now())
                .lastModifiedAt(LocalDateTime.now())
                .build();

        given(postService.getPostById(any(Integer.class))).willReturn(post);

        mockMvc.perform(get(String.format("/api/v1/posts/%d", postId))
                        .with(csrf()))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                .andExpect(jsonPath("$.result.id").value(1))
                .andExpect(jsonPath("$.result.title").value("title1"))
                .andExpect(jsonPath("$.result.body").value("body1"))
                .andExpect(jsonPath("$.result.userName").value("Soyeong"))
                .andExpect(jsonPath("$.result.createdAt").exists())
                .andExpect(jsonPath("$.result.lastModifiedAt").exists());
    }

    @Nested
    @DisplayName("포스트 수정 테스트")
    class UpdatePostTest {

        @Test
        @WithAnonymousUser
        @DisplayName("포스트 수정 실패 - 인증 실패")
        void authenticationFailed() throws Exception {
            PostReq req = PostReq.builder()
                    .title("test title")
                    .body("test body")
                    .build();
            UserEntity user = UserEntity.builder()
                    .userName("Soyeong")
                    .password("12345")
                    .build();
            Integer postId = 1;

            given(postService.updatePostById(any(Integer.class), any(PostReq.class), any(String.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.USERNAME_NOT_FOUND, String.format("%s은(는) 없는 회원입니다.", user.getUsername())));

            mockMvc.perform(put(String.format("/api/v1/posts/%d", postId))
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsBytes(req)))
                    .andDo(print())
                    .andExpect(status().isUnauthorized());
        }

        @Test
        @DisplayName("포스트 수정 실패 - 작성자 불일치")
        void mismatchedAuthorAndUser() throws Exception {
            PostReq req = PostReq.builder()
                    .title("test title")
                    .body("test body")
                    .build();
            Integer postId = 1;

            given(postService.updatePostById(any(Integer.class), any(PostReq.class), any(String.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.INVALID_PERMISSION, "사용자가 권한이 없습니다."));

            mockMvc.perform(put(String.format("/api/v1/posts/%d", postId))
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsBytes(req)))
                    .andDo(print())
                    .andExpect(status().isUnauthorized())
                    .andExpect(jsonPath("$.resultCode").value("ERROR"))
                    .andExpect(jsonPath("$.result.errorCode").value("INVALID_PERMISSION"))
                    .andExpect(jsonPath("$.result.message").value("사용자가 권한이 없습니다."));
        }

        @Test
        @DisplayName("포스트 수정 실패 - 데이터베이스 에러")
        void notFoundDatabase() throws Exception {
            PostReq req = PostReq.builder()
                    .title("test title")
                    .body("test body")
                    .build();
            Integer postId = 1;

            given(postService.updatePostById(any(Integer.class), any(PostReq.class), any(String.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.DATABASE_ERROR, "DB에러"));

            mockMvc.perform(put(String.format("/api/v1/posts/%d", postId))
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsBytes(req)))
                    .andDo(print())
                    .andExpect(status().isInternalServerError())
                    .andExpect(jsonPath("$.resultCode").value("ERROR"))
                    .andExpect(jsonPath("$.result.errorCode").value("DATABASE_ERROR"))
                    .andExpect(jsonPath("$.result.message").value("DB에러"));
        }

        @Test
        @DisplayName("포스트 수정 성공")
        void successUpdatePost() throws Exception {
            PostReq req = PostReq.builder()
                    .title("test title")
                    .body("test body")
                    .build();
            Integer postId = 1;

            given(postService.updatePostById(any(Integer.class), any(PostReq.class), any(String.class), any(String.class))).willReturn(new PostRes("포스트 수정 완료", postId));

            mockMvc.perform(put(String.format("/api/v1/posts/%d", postId))
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsBytes(req)))
                    .andDo(print())
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                    .andExpect(jsonPath("$.result.message").value("포스트 수정 완료"))
                    .andExpect(jsonPath("$.result.postId").value(postId));
        }
    }


    @Nested
    @DisplayName("포스트 삭제 테스트")
    class DeletePostTest {

        @Test
        @DisplayName("포스트 삭제 성공")
        void successDeletePost() throws Exception {
            Integer postId = 1;

            given(postService.deletePostById(any(Integer.class), any(String.class), any(String.class))).willReturn(new PostRes("포스트 삭제 완료", postId));

            mockMvc.perform(delete(String.format("/api/v1/posts/%d", postId))
                            .with(csrf()))
                    .andDo(print())
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                    .andExpect(jsonPath("$.result.message").value("포스트 삭제 완료"))
                    .andExpect(jsonPath("$.result.postId").value(postId));
        }

        @Test
        @WithAnonymousUser
        @DisplayName("포스트 삭제 실패 - 인증 실패")
        void authenticationFailed() throws Exception {
            UserEntity user = UserEntity.builder()
                    .userName("Soyeong")
                    .password("12345")
                    .build();
            Integer postId = 1;

            given(postService.deletePostById(any(Integer.class), any(String.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.USERNAME_NOT_FOUND, String.format("%s은(는) 없는 회원입니다.", user.getUsername())));

            mockMvc.perform(delete(String.format("/api/v1/posts/%d", postId))
                            .with(csrf()))
                    .andDo(print())
                    .andExpect(status().isUnauthorized());
        }

        @Test
        @DisplayName("포스트 삭제 실패 - 작성자 불일치")
        void mismatchedAuthorAndUser() throws Exception {
            Integer postId = 1;

            given(postService.deletePostById(any(Integer.class), any(String.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.INVALID_PERMISSION, "사용자가 권한이 없습니다."));

            mockMvc.perform(delete(String.format("/api/v1/posts/%d", postId))
                            .with(csrf()))
                    .andDo(print())
                    .andExpect(status().isUnauthorized())
                    .andExpect(jsonPath("$.resultCode").value("ERROR"))
                    .andExpect(jsonPath("$.result.errorCode").value("INVALID_PERMISSION"))
                    .andExpect(jsonPath("$.result.message").value("사용자가 권한이 없습니다."));
        }

        @Test
        @DisplayName("포스트 삭제 실패 - 데이터베이스 에러")
        void notFoundDatabase() throws Exception {
            Integer postId = 1;

            given(postService.deletePostById(any(Integer.class), any(String.class), any(String.class))).willThrow(new HealingSnsAppException(ErrorCode.DATABASE_ERROR, "DB에러"));

            mockMvc.perform(delete(String.format("/api/v1/posts/%d", postId))
                            .with(csrf()))
                    .andDo(print())
                    .andExpect(status().isInternalServerError())
                    .andExpect(jsonPath("$.resultCode").value("ERROR"))
                    .andExpect(jsonPath("$.result.errorCode").value("DATABASE_ERROR"))
                    .andExpect(jsonPath("$.result.message").value("DB에러"));
        }
    }

    @Nested
    @DisplayName("마이피드 조회 테스트")
    class GetMyFeed {

        @Test
        @DisplayName("마이피드 조회 성공")
        void successGetMyFeed() throws Exception {
            given(postService.getMyFeed(any(Pageable.class), any(String.class))).willReturn(Page.empty());

            mockMvc.perform(get("/api/v1/posts/my")
                            .param("page", "0")
                            .param("size", "10")
                            .param("sort", "createdAt,desc")
                            .param("userName", "user"))
                    .andDo(print())
                    .andExpect(status().isOk());
        }

        @Test
        @WithAnonymousUser
        @DisplayName("마이피드 조회 실패 - 로그인 하지 않은 경우")
        void NotLogin() throws Exception {
            given(postService.getMyFeed(any(Pageable.class), any(String.class))).willReturn(Page.empty());

            mockMvc.perform(get("/api/v1/posts/my")
                            .param("page", "0")
                            .param("size", "10")
                            .param("sort", "createdAt,desc")
                            .param("userName", "user"))
                    .andDo(print())
                    .andExpect(status().isUnauthorized());
        }
    }

}

@Nested 적용 후

이렇게 @Nested 를 활용하면 Test 코드는 길어지지만 결과가 계층구조로 나오기 때문에 훨씬 가독성 있게 테스트를 확인할 수 있다.

또한, @DisplayName("테스트이름")을 함께 작성하면 메서드명이 아닌 지정한 테스트명으로 테스트 결과를 더 알아보기 쉽다.

Comments