개발 낙서장

[TIL] 내일배움캠프 41일차 - Controller 테스트 404 본문

Java/Sparta

[TIL] 내일배움캠프 41일차 - Controller 테스트 404

권승준 2024. 2. 22. 22:11

 

 

오늘의 학습 키워드📚

Controller 테스트 404 에러

TodoCard Controller 테스트가 잘 진행돼서 Comment Controller 테스트 코드 작성을 하던 중에 진짜 도저히 이해가 안 되는 에러를 만났다.

바로 404 에러.....

404 에러는 99%의 경우로 URL을 잘못 입력했거나 GET, POST 등 메소드를 다르게 입력했을 때 발생한다. 근데 나는 URL도 그대로 입력했고 메소드도 제대로 했는데 계속 404가 발생해서 도저히 이유를 찾을 수 없었다.

@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class CommentController {

    private final CommentService commentService;

    @PostMapping("/comments/card-id/{cardId}")
    public ResponseEntity<ResponseMessage<CommentResponseDto>> createComment(@PathVariable Long cardId,
        @Valid @RequestBody CommentRequestDto requestDto,
        @AuthenticationPrincipal UserDetailsImpl userDetails,
        BindingResult bindingResult) {

        ValidationUtil.validateRequestDto(bindingResult);

        CommentResponseDto responseDto = commentService.createComment(cardId, requestDto,
            userDetails.getUser());

        return ResponseEntity.ok().body(
            ResponseMessage.<CommentResponseDto>builder()
                .msg(responseDto.getContent() + " 댓글 작성 성공!")
                .httpCode(200)
                .data(responseDto).build()
        );
    }
}
    @Test
    @DisplayName("Create Comment")
    void createComment() throws Exception {
        // given
        CommentRequestDto requestDto = CommentRequestDto.builder().content("댓글 내용").build();

        String postInfo = objectMapper.writeValueAsString(requestDto);

        // when-then
        mockMvc.perform(
                post("/api/comments/card-id/100").content(postInfo)
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON).principal(principal)).andExpect(status().isOk())
            .andDo(print());
    }

차례대로 Controller의 createComment 부분과 테스트 코드의 createComment이다.
URL도 똑같고 requestBody나 PathVariable이나 contentType도 틀리게 입력한 게 없었다.
URL을 바꿔도 보고 ID 입력 방식을 다르게도 해보고 뭐 @Import 어노테이션, @ComponentScan 어노테이션 등 갖가지 방법으로 해결을 시도했는데도 안 됐다.

그래서 디버그를 찍어봐도 안 나오길래 몇 시간 동안 헤매다가 그냥 에러 발생 원인부터 하나하나 지워나갔는데 이유를 바로 찾았다.
결국 200이 아니라 404가 떠서 테스트가 진행이 안 되는 것이므로 andExpect 부분을 지우고 실행했더니 이런 결과가 나왔다.

MockHttpServletResponse:
           Status = 404
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"httpCode":404,"msg":"Cannot invoke \"com.sparta.mytodo.dto.CommentResponseDto.getContent()\" because \"responseDto\" is null","data":null}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

responseDto가 null 이라서 CommentResponseDto의 getContent 메소드를 호출할 수가 없다는 것이었다.

아니 이런 말도 안 되는!!!!! 그래서 Controller 부분의 응답을 수정했더니 정상적으로 동작하기는 했다....

하지만 문제는 또 있었다. 결국 응답 값이 null이라 계속 에러가 발생했던 것이다.
분명 RequestBody를 잘 담아서 보냈는데 왜 응답이 null이지?

given(commentService.createComment(100L, requestDto, user)).willReturn(commentResponseDto);

아! BDDMockito의 given으로 행동을 정해주면 되겠구나!

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"httpCode":200,"msg":"댓글 작성 성공!","data":null}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

🤨 ...?
대체 왜 이런 거지 하고 또다시 구글링의 늪에 빠져서 내린 결론은

요청으로 넘기는 인스턴스와 Controller의 인스턴스는 '다른 인스턴스'로 취급되기에 아예 다른 객체가 넘어온 것이다.
즉 인스턴스가 일치하지 않아서 테스트 환경에서 제대로 처리되지 않아 null이 넘어온 것이다.
(내가 내린 결론은 이런데 실제론 아예 다른 이유 때문일 수도 있다...)

그래서 ArgumentMatchersany()라는 메소드를 사용했다.
어떤 값이 넘어오든 클래스에 맞게 넘어온다면 응답을 받을 수 있도록 하는 메소드라고 한다.
즉 인스턴스가 달라도 클래스 형식만 맞게 보낸다면 정상적으로 처리돼 응답이 되는 방식이다.

    @Test
    @DisplayName("Create Comment")
    void createComment() throws Exception {
        // given
        CommentRequestDto requestDto = CommentRequestDto.builder().content("댓글 내용").build();
        Comment comment = new Comment(requestDto, user, todoCard);
        CommentResponseDto commentResponseDto = new CommentResponseDto(comment);

        String postInfo = objectMapper.writeValueAsString(requestDto);
        
        given(commentService.createComment(anyLong(), any(CommentRequestDto.class),
            any(User.class))).willReturn(commentResponseDto);

        // when-then
        mockMvc.perform(
                post("/api/comments/card-id/100").content(postInfo)
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON).principal(principal)).andExpect(status().isOk())
            .andDo(print());
    }
MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"httpCode":200,"msg":"댓글 내용 댓글 작성 성공!","data":{"username":"abc123","cardname":"카드 이름입니다","content":"댓글 내용","cratedAt":null,"modifiedAt":null}}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

와 세상에... 도대체 응답이 돼야 하는데 안 돼서 얼마나 많은 방법들을 시도했는지 모르겠다.
테스트 코드를 쉽게 생각했는데 생각보다 복잡하고 배워야 할 것들이 많은 것 같다....


오늘의 회고💬

오랜만에 제대로 이유 모를 문제를 만나서 몇 시간 고생한 것 같다. 그래도 개발적인 부분으로 해결해서 기분은 완전 상쾌하다!!!

 

내일의 계획📜

개인 과제는 이것으로 끝이 났고 이제 팀 과제가 시작된다. 화이팅!

Comments