되자!백엔드개발자

[ERROR] Mock 테스트 중 HttpMessageNotReadableException에러(400에러) 본문

개발공부/ERROR

[ERROR] Mock 테스트 중 HttpMessageNotReadableException에러(400에러)

HyunJng 2023. 8. 5. 20:19

에러 내용


Controller에 대한 테스트 코드를 짜는데 해당 URL의 Controller에 메소드까지 도달하지 못하고 400에러가 났다.

제대로 JSON처리도 했고 @RequestBody도 만들었고

기존에 비슷한 Controller 테스트를 진행했을 때는 잘 됐었는데???!

 

안되는 원인을 도저히 떠오르지 않았고 경우의 수를 두고 디버깅해본 결과

@RequestBody로 받는 인자가 있을 때 에러가 발생하는 것을 확인하고 열심히 구글링 시작...

 

테스트코드

    @Test
    @DisplayName("POST /v1/mentee/{mentoringId} 요청시 해당 멘토링에 멘티 신청을 한다. " +
            "멘티신청시 멘토링 count도 +1 되어야한다.")
    public void applyMentee() throws Exception {
        // given
        CreateApplyMenteeReq requestDto = new CreateApplyMenteeReq("멘토링신청합니다.");

        // when
        mockMvc.perform(MockMvcRequestBuilders.post("/v1/mentee/{mentoringId}", mentoring.getId())
                        .contentType(MediaType.APPLICATION_JSON)
                        .header("Authorization", "Bearer " + accessToken)
                        .content(mapper.writeValueAsString(requestDto))
                ).andExpect(status().isOk())
                .andDo(print());
        // then
        System.out.println("requestDto = " + requestDto);
    }

컨트롤러

    @PostMapping("/{mentoringId}")
    public ResponseEntity<Long> applyMentee(@PathVariable("mentoringId") Long mentoringId, @RequestBody CreateApplyMenteeReq dto) {
        Long applyMenteeId = menteeService.createApplyMentee(mentoringId, dto);
        return ResponseEntity.ok(applyMenteeId);
    }

RequestDTO

@ToString
@Getter
@AllArgsConstructor
public class CreateApplyMenteeReq {
    private final String description;

	// to entity 메서드
}

 

원인


이전에 진행했던 Controller에서는 RequestDTO로 클라이언트에게 받는 필드가 두개 이상이었으나

이번에 테스트한 메서드에서는 필드가 하나 뿐이어서 직렬화 오류가 났던 것이 원인이었다.

 

그럼 왜 필드가 하나라면 오류가 날까?

컨트롤러에 DTO가 전달되기 위해서는 ArgumentResolver이 JSON데이터를 Object로 변환하기 위해 MessageConverter을 사용하게 된다.

 

MessageConverter는 역질렬화를 할 때 아래와 같은 과정을 거치게 된다.

1. Object 생성 : 기본생성자를 호출 (예외: Property 사용 예제)
2. Object 필드 인식 :Setter 혹은 Getter을 이용
3. Object 필드에 값 넣기 : Reflection이용(setter이용X)

만약 해당 클래스(여기서는 RequestDTO)에 기본 생성자가 없을 떄

객체를 생성할 수 있는 다른 방법도 제공해준다.

1. delegateDeser이 null이 아니거나 

2. property생성자가 있을경우

이 둘에 해당하지 않으면 역직렬화를 하지 못하고 400에러가 발생했던 것!

 

1번은 솔직히 뭔지 모르지만 난 설정 안해줬으니 넘어가자

2번은 property생성자가 있었는데 이 경우에 해당되는거 아닌가 싶지만

인자를 하나만 받는 property생성자만 존재한다면 또 상황이 달라진다.

 

Jackson에서 기본생성자와 getter, setter이 없으면

자동적으로 @JsonCreator를 해당 인자를 받는 생성자에 붙여서 동작하도록 도와준다.

+) @JsonCreator은 역직렬화에 사용되는 생성자를 지정해주는 어노테이션이다.

하지만 단일 생성자에 단일 파라미터가 있는 경우

JackSon이 @JsonCreator을 추가해주는 기능이 동작하지 않아서 해당 에러가 발생했던 것이다.

 

해결방법


직접 @JsonCreator 붙여준다.

@ToString
@Getter
public class CreateApplyMenteeReq {
    private final String description;

    @JsonCreator
    public CreateApplyMenteeReq(String description) {
        this.description = description;
    }
 }

참고

https://blogshine.tistory.com/445

https://yeonyeon.tistory.com/221