test 코드 작성 중 LocalDateTime 관련 에러
PostControllerTest
@SpringBootTest
@AutoConfigureMockMvc // mock 빈 등록
@ExtendWith(MockKExtension::class)
class PostControllerTest @Autowired constructor(
private val mockMvc: MockMvc, private val jwtPlugin: JwtPlugin
): DescribeSpec({
extension(SpringExtension) //Junit5 확장
afterContainer { clearAllMocks() }
val postService = mockk<PostService>()
describe("GET/posts/{postId}") {
context("존재하는 ID를 요청할 때") {
it("http 상태 코드 200을 반환한다.") {
val postId = 10L
every { postService.getPost(any()) } returns PostResponse(
id = postId,
title = "postTitle",
content = "postContest",
status = PostStatus.FALSE,
author = "postAuthor",
createdAt = LocalDateTime.now(),
comments = mutableListOf()
)
val jwtToken = jwtPlugin.generateAccessToken("test", "test", "USER")
val result = mockMvc.perform( // mockMvc 로 특정 요청 실행
get("/posts/$postId")
.header("Authorization", "Bearer $jwtToken")
).andReturn()
result.response.status shouldBe 200
val responseDto = jacksonObjectMapper().readValue(
result.response.contentAsString,
PostResponse::class.java
)
responseDto.id shouldBe postId
}
}
}
})
문제
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
at [Source: (String)"{"id":10,"title":"2","content":"2","status":"FALSE","author":"1","createdAt":"2024-02-06T21:01:06.38197","comments":[]}"; line: 1, column: 78] (through reference chain: org.example.todolist.domain.post.dto.PostResponse["createdAt"])
반환값의 내용을 가져오기 위해 jacksonObjectMapper() 을 사용한 부분에서
Java8 Jackson 라이브러리가 LocalDateTime 과 같은 날짜/시간 유형을 처리하지 못함
날짜/시간 유형을 직렬화/역직렬화하는 기능이 없기 때문
찾은 방법
참조 : https://woo-chang.tistory.com/75 / https://tlatmsrud.tistory.com/114
jackson-datatype-jsr310 모듈 추가
implementation ("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
implementation ("com.fasterxml.jackson.core:jackson-databind")
해당 클래스에 어노테이션 추가
@JsonSerialize(using = LocalDateTimeSerializer::class)
@JsonDeserialize(using = LocalDateTimeDeserializer::class)
모듈 config
@Configuration
class ModuleConfig {
@Bean
fun objectMapper(): ObjectMapper {
val objectMapper = ObjectMapper()
objectMapper.registerModules(JavaTimeModule())
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
return objectMapper
}
}
찾아본 방법 다 했는데 똑같은 에러가 나온다.
https://umanking.github.io/2021/07/24/jackson-localdatetime-serialization/
해결
ObejectMapper
: json 을 java 객체로 역직렬화하거나 java 객체를 json 으로 직렬화할 때 사용하는 jackson 라이브러리의 클래스이다.
컬렉션 타입, 날짜/시간 타입과 같이 복잡한 데이터 유형을 적절하게 처리하기 위해서는 사용자 정의 모듈을 추가해준다.
❗jsr310 은 스프링 부트에서 기본적으로 가져와준다. => gradle 에서 삭제
❗ 테스트코드 안에 objectmapper 를 직접 생성해주고 jacksonObjectMapper 가 아닌 objectMapper 을 사용
=> 실패, 다른 에러 ("PostResponse 인스턴스를 생성할 수 없다")
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.example.todolist.domain.post.dto.PostResponse` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
response 객체에 생성자를 만들어줬다.
data class PostResponse(
val id: Long,
val title: String,
val content: String,
val status: PostStatus,
val author: String,
val createdAt: LocalDateTime,
val comments: List<CommentResponse>
) {
constructor() : this(0, "", "", PostStatus.FALSE, "", LocalDateTime.now(), mutableListOf())
}
=> 통과
어느 부분이 만족한건지 모르겠어서 여러가지를 주석처리했다가 다시 실행해봤다.
❗ SerializationFeature 삭제
@Configuration
class ModuleConfig {
@Bean
fun objectMapper(): ObjectMapper {
val objectMapper = ObjectMapper()
objectMapper.registerModules(JavaTimeModule())
//objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
return objectMapper
}
}
=> 통과
직렬화 write 부분 disable 한 것은 상관없는 것 같다.
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
: Date 를 Timestamps 로 찍어내는 직렬화
❗objectMapper 가 아닌 jacksonObjectMapper 를 사용했을 때
=> 같은 오류로 실패
jacksonObjectMapper()
: kotlin 에서 jackson 라이브러리를 사용할 때 제공되는 확장함수, objectmapper 의 인스턴스를 생성하고 구성
❗ objectMapper 빈 등록만 했을 때
LocalDateTime 문제를 찾아봤을 때 obejctMapper 을 따로 설정없이 해주는게
문제라 javatimeModule 을 등록해주어야한다는 부분이 있었다.
@Bean
fun objectMapper(): ObjectMapper {
val objectMapper = ObjectMapper()
// objectMapper.registerModules(JavaTimeModule())
// objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
return objectMapper
}
=> 실패
JavatimeModule 설정이 핵심인 것 같다.
JavatimeModule
: ObjectMapper 에서 제공하는 모듈 중 하나로 날짜/시간 타입의 데이터를 처리해준다.
❗ 테스트코드가 아닌 다른 파일에 config 클래스를 작성해서 생성한 뒤
테스트코드에서 objectMapper 을 주입받아서 실행
=> 실패
결론
처음 찾아본 해결방법에서 답이 적혀있었는데 빙빙 돌아왔다...
ObejectMapper https://woo-chang.tistory.com/75
: json 을 java 객체로 역직렬화하거나 java 객체를 json 으로 직렬화할 때 사용하는 jackson 라이브러리의 클래스이다.
컬렉션 타입, 날짜/시간 타입과 같이 복잡한 데이터 유형을 적절하게 처리하기 위해서는 사용자 정의 모듈을 추가해준다.
JavatimeModule
: java8 에서 도입된 날짜 및 시간 API (LocalDate, LocalTime, LocalDateTiem) 을 jackson 라이브러리에서 인식하지 못하기 때문에 직렬화/역직렬화에서 문제가 발생한다. JavatimeModule 은 ObjectMapper 에서 제공하는 모듈 중 하나로 이러한 날짜 및 시간 API 를 jackson 라이브러리에서 날짜/시간 타입의 데이터를 처리해준다.
=> 스프링 부트 프로젝트에서 LocalDateTime 형의 데이터를 직렬화/역직렬화하기 위해 objectMapper() 를 사용할 때 날짜/시간 데이터를 처리해주기 위한 JavatimeModule 을 추가해준다.
@Bean
fun objectMapper(): ObjectMapper {
val objectMapper = ObjectMapper()
objectMapper.registerModules(JavaTimeModule()) // 날짜 및 시간 처리
return objectMapper
}
🚩 더 찾아볼 점
❓ 강의에서는 jacksonObjectMapper 를 사용해 별다른 에러 없이 잘 이용됐는데 어떻게 된걸까?
- 게시글 생성날짜 타입이 LocalDateTime 이 아닌 다른건지?
❓ 별도의 config 파일을 따로 빼서 만들었을 때 왜 사용이 안되지?
=> 코드 직접 작성한 분께 물어봐야할 것 같다.
'왕초보일지' 카테고리의 다른 글
240213 TIL | 테스트 코드 (0) | 2024.02.13 |
---|---|
240208 TIL | (0) | 2024.02.08 |
240206 TIL | (1) | 2024.02.06 |
240202 TIL | validation / spring aop / querydsl (0) | 2024.02.02 |
복습과제 (0) | 2024.02.02 |