diff --git a/src/main/java/roomescape/common/config/JacksonConfig.kt b/src/main/java/roomescape/common/config/JacksonConfig.kt index dc0a4cce..418ddbb9 100644 --- a/src/main/java/roomescape/common/config/JacksonConfig.kt +++ b/src/main/java/roomescape/common/config/JacksonConfig.kt @@ -1,40 +1,41 @@ -package roomescape.common.config; +package roomescape.common.config -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer +import com.fasterxml.jackson.module.kotlin.kotlinModule +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import java.time.LocalDate +import java.time.LocalTime +import java.time.format.DateTimeFormatter @Configuration -public class JacksonConfig { +class JacksonConfig { - @Bean - public ObjectMapper objectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerModule(javaTimeModule()); - return objectMapper; - } + @Bean + fun objectMapper(): ObjectMapper = ObjectMapper() + .registerModule(javaTimeModule()) + .registerModule(kotlinModule()) - @Bean - public JavaTimeModule javaTimeModule() { - JavaTimeModule javaTimeModule = new JavaTimeModule(); - - javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE)); - javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE)); - - javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm"))); - javaTimeModule.addDeserializer(LocalTime.class, - new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm"))); - - return javaTimeModule; - } + private fun javaTimeModule(): JavaTimeModule = JavaTimeModule() + .addSerializer( + LocalDate::class.java, + LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE) + ) + .addDeserializer( + LocalDate::class.java, + LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE) + ) + .addSerializer( + LocalTime::class.java, + LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm")) + ) + .addDeserializer( + LocalTime::class.java, + LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm")) + ) as JavaTimeModule } diff --git a/src/test/java/roomescape/common/JacksonConfigTest.java b/src/test/java/roomescape/common/JacksonConfigTest.java deleted file mode 100644 index 54610fa5..00000000 --- a/src/test/java/roomescape/common/JacksonConfigTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package roomescape.common; - -import static org.assertj.core.api.Assertions.*; - -import java.time.LocalDate; -import java.time.LocalTime; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import roomescape.common.config.JacksonConfig; - -class JacksonConfigTest { - - private ObjectMapper objectMapper; - - @BeforeEach - void setUp() { - JacksonConfig jacksonConfig = new JacksonConfig(); - objectMapper = jacksonConfig.objectMapper(); - } - - @DisplayName("날짜는 yyyy-MM-dd 형식으로 직렬화 된다.") - @Test - void dateSerialize() throws JsonProcessingException { - // given - LocalDate date = LocalDate.parse("2021-07-01"); - - // when - String json = objectMapper.writeValueAsString(date); - LocalDate actual = objectMapper.readValue(json, LocalDate.class); - - // then - assertThat(actual.toString()).isEqualTo("2021-07-01"); - } - - @DisplayName("시간은 HH:mm 형식으로 직렬화된다.") - @Test - void timeSerialize() throws JsonProcessingException { - // given - LocalTime time = LocalTime.parse("12:30:00"); - - // when - String json = objectMapper.writeValueAsString(time); - LocalTime actual = objectMapper.readValue(json, LocalTime.class); - - // then - assertThat(actual.toString()).isEqualTo("12:30"); - } - - @DisplayName("yyyy-MM-dd 형식의 문자열은 LocalDate로 역직렬화된다.") - @Test - void dateDeserialize() throws JsonProcessingException { - // given - String json = "\"2021-07-01\""; - - // when - LocalDate actual = objectMapper.readValue(json, LocalDate.class); - - // then - assertThat(actual).isEqualTo(LocalDate.of(2021, 7, 1)); - } - - @DisplayName("HH:mm 형식의 문자열은 LocalTime으로 역직렬화된다.") - @Test - void timeDeserialize() throws JsonProcessingException { - // given - String json = "\"12:30\""; - - // when - LocalTime actual = objectMapper.readValue(json, LocalTime.class); - - // then - assertThat(actual).isEqualTo(LocalTime.of(12, 30)); - } -} diff --git a/src/test/java/roomescape/common/config/JacksonConfigTest.kt b/src/test/java/roomescape/common/config/JacksonConfigTest.kt new file mode 100644 index 00000000..5033c0d6 --- /dev/null +++ b/src/test/java/roomescape/common/config/JacksonConfigTest.kt @@ -0,0 +1,55 @@ +package roomescape.common.config + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.exc.InvalidFormatException +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import java.time.LocalDate +import java.time.LocalTime + +class JacksonConfigTest( + private val objectMapper: ObjectMapper = JacksonConfig().objectMapper() +) : FunSpec({ + + context("날짜는 yyyy-mm-dd 형식이다.") { + val date = "2025-07-14" + val serialized: String = objectMapper.writeValueAsString(LocalDate.parse(date)) + val deserialized: LocalDate = objectMapper.readValue(serialized, LocalDate::class.java) + + test("LocalDate 직렬화") { + serialized shouldBe "\"$date\"" + } + + test("LocalDate 역직렬화") { + deserialized shouldBe LocalDate.parse(date) + } + + test("형식이 잘못되면 InvalidFormatException을 던진다.") { + shouldThrow { + objectMapper.readValue("\"2025/07/14\"", LocalDate::class.java) + }.message shouldContain "Text '2025/07/14' could not be parsed" + } + } + + context("시간은 HH:mm 형식이다.") { + val (hour, minute, sec) = Triple(12, 30, 45) + val serialized: String = objectMapper.writeValueAsString(LocalTime.of(hour, minute, sec)) + val deserialized: LocalTime = objectMapper.readValue(serialized, LocalTime::class.java) + + test("LocalTime 직렬화") { + serialized shouldBe "\"$hour:$minute\"" + } + + test("LocalTime 역직렬화") { + deserialized shouldBe LocalTime.of(hour, minute) + } + + test("형식이 잘못되면 InvalidFormatException을 던진다.") { + shouldThrow { + objectMapper.readValue("\"$hour:$minute:$sec\"", LocalTime::class.java) + }.message shouldContain "Text '$hour:$minute:$sec' could not be parsed" + } + } +})