e-commerce 대용량 서버 프로젝트를 진행하던 중, Controller에서의 통합테스트가 어떠한 오류로 인해 실패하였습니다. 제가 겪었던 해당 오류의 해결과정을 기록해보려고 합니다.
해당 프로젝트는 다음 github에서 확인 가능합니다.
https://github.com/f-lab-edu/online-marketplace
@DisplayName("이메일이 중복된 회원이 아니라면 회원 가입에 성공한다.")
@Test
void signUp() throws Exception {
// given
final SignUpRequestDto dto = SignUpRequestDto.builder()
.name(User1.NAME)
.email(User1.EMAIL)
.password(User1.PASSWORD)
.phone(User1.PHONE)
.build();
// when
final ResultActions actions = mvc.perform(post("/users/sign-up")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(dto)))
.andDo(print());
// then
actions
.andExpect(status().isCreated())
.andDo(print());
}
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.coupang.marketplace.controller.SignUpRequestDto]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.coupang.marketplace.controller.SignUpRequestDto` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (PushbackInputStream); line: 1, column: 2]
...(이하 생략)
해당 오류를 보면 SignUpRequestDto에 생성자가 없어 cannot deserialize from Object value
하여 InvalidDefinitionException이 발생한다고 합니다. 즉, 역직렬화 과정에서 문제가 있는 듯 싶은데.. 그렇다면 일단 SignUpRequestDto클래스를 보겠습니다.
@Getter
public class SignUpRequestDto {
@NotBlank(message = "이름을 입력해주세요.")
private String name;
@NotBlank(message = "이메일을 입력해주세요.")
@Email(message = "이메일 양식을 지켜주세요.")
private String email;
@NotBlank(message = "비밀번호를 입력해주세요.")
private String password;
@NotBlank(message = "휴대폰 번호를 입력해주세요.")
@Pattern(regexp="[0-9]{10,11}", message = "10-11 자리의 전화번호를 입력해야 해요.")
private String phone;
@Builder
public SignUpRequestDto(String name, String email, String password, String phone) {
this.name = name;
this.email = email;
this.password = password;
this.phone = phone;
}
public SignUpRequestDto(String name, String email, String password, String phone){...}
은 분명 생성자가 맞는데.. 왜 생성자가 없다고 하는 걸까요?
오류를 이어서 살펴보면,
노란 박스가 가리키는 곳은 다음과 같습니다.
여기서 writeValueAsString()
메소드는 Java오브젝트인 dto를 JSON으로 serializing(직렬화) 를 합니다. 즉 ObjectMapper로 dto를 JSON으로 변환합니다.
하지만 오류내용에서 보았듯이 deserialize(역직렬화) 하는 과정에서 문제가 발생한 듯 싶습니다.
😎여기서 멘토님이 힌트를 주셨습니다😎
생성자의 인자명은 컴파일시에 이름이 바뀌기 때문에 생성자를 찾지 못하는 경우가 있다
jackson은 역직렬화할때 생성자나 setter을 사용하는데 인자 이름이 바뀌어 버린 생성자를 찾지 못해 매핑시킬 수 없는 것입니다.
그럼 jackson이 생성자를 찾도록 해주려면 어떻게 해야할까요?
저는 @JsonProperty
를 이용해보았습니다.
@Builder
public SignUpRequestDto(@JsonProperty("name") String name, @JsonProperty("email") String email, @JsonProperty("password") String password, @JsonProperty("phone") String phone) {
this.name = name;
this.email = email;
this.password = password;
this.phone = phone;
}
SignUpRequestDto생성자의 각 파라미터에 @JsonProperty
를 사용하여 인자를 원하는 이름으로 JSON변환 시킬 수 있습니다.
즉 @JsonProperty("name") String name
인 경우에는 인자명 name
이 JSON으로 변환되어도 지정된 name
이라는 이름을 유지할 수 있습니다. 당연히 필요시에는 다른이름으로도 설정이 가능합니다.
그렇게 인자명을 지정시키면 jackson이 해당 인자들을 가진 생성자를 찾을 수 있어 매핑이 가능하게 됩니다.
알고나서 오류 내용을 다시 보았을 때, 이제서야 보이는 해답..
(no delegate- or property-based Creator)
이미 정답을 알려주고 있었습니다..^^
하지만 이 오류해결 과정을 거치지 않았다면 이해를 할 수 없었겠죠😤
반면, 다음과 같은 방법이 아니더라도 build tool
을 변경하여 오류를 해결할 수 있습니다.
preference
- build, execution, deployment
- build tools
- gradle
로 따라 들어가면 해당 화면을 볼 수 있는데, 빌드와 실행을 Gradle
을 사용한다면 오류가 발생하지 않습니다.
https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/ObjectMapper.html
https://fasterxml.github.io/jackson-annotations/javadoc/2.6/com/fasterxml/jackson/annotation/JsonProperty.html
생성자의 인자명은 컴파일시에 이름이 바뀌기 때문에 생성자를 찾지 못하는 경우가 있다
알고 있었던 개념 같은데 까먹었어요. 다시 리와인드 하게 해주셔서 감사합니다.