From ea047d38bbc71f346e3345c92f262906212f7bc4 Mon Sep 17 00:00:00 2001 From: pricelees Date: Mon, 4 Aug 2025 19:52:15 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=83=88=EB=A1=9C=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=EB=90=9C=20TimeFinder,Writer,Validator=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EA=B8=B0=EC=A1=B4?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../business/domain/TimeWithAvailability.kt | 3 + .../time/business/TimeServiceTest.kt | 117 ++++++++++++---- .../time/implement/TimeFinderTest.kt | 109 +++++++++++++++ .../time/implement/TimeValidatorTest.kt | 74 ++++++++++ .../time/implement/TimeWriterTest.kt | 84 +++++++++++ .../roomescape/time/web/TimeControllerTest.kt | 131 +++++++++--------- 6 files changed, 425 insertions(+), 93 deletions(-) create mode 100644 src/test/kotlin/roomescape/time/implement/TimeFinderTest.kt create mode 100644 src/test/kotlin/roomescape/time/implement/TimeValidatorTest.kt create mode 100644 src/test/kotlin/roomescape/time/implement/TimeWriterTest.kt diff --git a/src/main/kotlin/roomescape/theme/business/domain/TimeWithAvailability.kt b/src/main/kotlin/roomescape/theme/business/domain/TimeWithAvailability.kt index 13500de3..9bae4dbd 100644 --- a/src/main/kotlin/roomescape/theme/business/domain/TimeWithAvailability.kt +++ b/src/main/kotlin/roomescape/theme/business/domain/TimeWithAvailability.kt @@ -16,4 +16,7 @@ class TimeWithAvailability( startAt = startAt, isAvailable = isReservable ) + + // for test + fun canReserve(): Boolean = isReservable } diff --git a/src/test/kotlin/roomescape/time/business/TimeServiceTest.kt b/src/test/kotlin/roomescape/time/business/TimeServiceTest.kt index 73266aaf..6a6893c4 100644 --- a/src/test/kotlin/roomescape/time/business/TimeServiceTest.kt +++ b/src/test/kotlin/roomescape/time/business/TimeServiceTest.kt @@ -1,38 +1,42 @@ package roomescape.time.business +import io.kotest.assertions.assertSoftly import io.kotest.assertions.throwables.shouldNotThrow import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe import io.mockk.Runs import io.mockk.every import io.mockk.just import io.mockk.mockk -import org.springframework.data.repository.findByIdOrNull -import roomescape.reservation.infrastructure.persistence.ReservationRepository +import roomescape.theme.business.domain.TimeWithAvailability +import roomescape.theme.exception.ThemeErrorCode +import roomescape.theme.exception.ThemeException import roomescape.time.exception.TimeErrorCode import roomescape.time.exception.TimeException -import roomescape.time.infrastructure.persistence.TimeRepository +import roomescape.time.implement.TimeFinder +import roomescape.time.implement.TimeWriter +import roomescape.time.infrastructure.persistence.TimeEntity import roomescape.time.web.TimeCreateRequest -import roomescape.util.TsidFactory import roomescape.util.TimeFixture +import java.time.LocalDate import java.time.LocalTime class TimeServiceTest : FunSpec({ - val timeRepository: TimeRepository = mockk() - val reservationRepository: ReservationRepository = mockk() + val timeFinder: TimeFinder = mockk() + val timeWriter: TimeWriter = mockk() - val timeService = TimeService( - tsidFactory = TsidFactory, - timeRepository = timeRepository, - reservationRepository = reservationRepository - ) + val timeService = TimeService(timeFinder, timeWriter) - context("findTimeById") { + context("findById") { test("시간을 찾을 수 없으면 예외 응답") { val id = 1L - every { timeRepository.findByIdOrNull(id) } returns null + every { + timeFinder.findById(id) + } throws TimeException(TimeErrorCode.TIME_NOT_FOUND) shouldThrow { timeService.findById(id) @@ -42,22 +46,81 @@ class TimeServiceTest : FunSpec({ } } + context("findTimes") { + test("정상 응답") { + val times: List = listOf( + TimeFixture.create(startAt = LocalTime.now()), + TimeFixture.create(startAt = LocalTime.now().plusMinutes(1)), + TimeFixture.create(startAt = LocalTime.now().plusMinutes(2)) + ) + + every { + timeFinder.findAll() + } returns times + + val response = timeService.findTimes() + + assertSoftly(response.times) { + it shouldHaveSize times.size + it.map { time -> time.startAt } shouldContainExactly times.map { time -> time.startAt } + } + } + } + + context("findTimesWithAvailability") { + val date = LocalDate.now() + val themeId = 1L + + test("정상 응답") { + val times: List = listOf( + TimeWithAvailability(1, LocalTime.now(), date, themeId, true), + TimeWithAvailability(2, LocalTime.now().plusMinutes(1), date, themeId, false), + TimeWithAvailability(3, LocalTime.now().plusMinutes(2), date, themeId, true) + ) + + every { + timeFinder.findAllWithAvailabilityByDateAndThemeId(date, themeId) + } returns times + + val response = timeService.findTimesWithAvailability(date, themeId) + + assertSoftly(response.times) { + it shouldHaveSize times.size + it.map { time -> time.isAvailable } shouldContainExactly times.map { time -> time.canReserve() } + } + } + + test("테마를 찾을 수 없으면 예외 응답") { + every { + timeFinder.findAllWithAvailabilityByDateAndThemeId(date, themeId) + } throws ThemeException(ThemeErrorCode.THEME_NOT_FOUND) + + shouldThrow { + timeService.findTimesWithAvailability(date, themeId) + }.also { + it.errorCode shouldBe ThemeErrorCode.THEME_NOT_FOUND + } + } + } + context("createTime") { val request = TimeCreateRequest(startAt = LocalTime.of(10, 0)) test("정상 저장") { - every { timeRepository.existsByStartAt(request.startAt) } returns false - every { timeRepository.save(any()) } returns TimeFixture.create( - id = 1L, - startAt = request.startAt - ) + val time: TimeEntity = TimeFixture.create(startAt = request.startAt) + + every { + timeWriter.create(request.startAt) + } returns time val response = timeService.createTime(request) - response.id shouldBe 1L + response.id shouldBe time.id } test("중복된 시간이 있으면 예외 응답") { - every { timeRepository.existsByStartAt(request.startAt) } returns true + every { + timeWriter.create(request.startAt) + } throws TimeException(TimeErrorCode.TIME_DUPLICATED) shouldThrow { timeService.createTime(request) @@ -67,14 +130,13 @@ class TimeServiceTest : FunSpec({ } } - context("removeTimeById") { + context("deleteTime") { test("정상 제거 및 응답") { val id = 1L val time = TimeFixture.create(id = id) - every { timeRepository.findByIdOrNull(id) } returns time - every { reservationRepository.findAllByTime(time) } returns emptyList() - every { timeRepository.delete(time) } just Runs + every { timeFinder.findById(id) } returns time + every { timeWriter.delete(time) } just Runs shouldNotThrow { timeService.deleteTime(id) @@ -84,7 +146,7 @@ class TimeServiceTest : FunSpec({ test("시간을 찾을 수 없으면 예외 응답") { val id = 1L - every { timeRepository.findByIdOrNull(id) } returns null + every { timeFinder.findById(id) } throws TimeException(TimeErrorCode.TIME_NOT_FOUND) shouldThrow { timeService.deleteTime(id) @@ -97,9 +159,8 @@ class TimeServiceTest : FunSpec({ val id = 1L val time = TimeFixture.create() - every { timeRepository.findByIdOrNull(id) } returns time - - every { reservationRepository.findAllByTime(time) } returns listOf(mockk()) + every { timeFinder.findById(id) } returns time + every { timeWriter.delete(time) } throws TimeException(TimeErrorCode.TIME_ALREADY_RESERVED) shouldThrow { timeService.deleteTime(id) diff --git a/src/test/kotlin/roomescape/time/implement/TimeFinderTest.kt b/src/test/kotlin/roomescape/time/implement/TimeFinderTest.kt new file mode 100644 index 00000000..6fb1c3a7 --- /dev/null +++ b/src/test/kotlin/roomescape/time/implement/TimeFinderTest.kt @@ -0,0 +1,109 @@ +package roomescape.time.implement + +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.springframework.data.repository.findByIdOrNull +import roomescape.reservation.implement.ReservationFinder +import roomescape.theme.business.domain.TimeWithAvailability +import roomescape.theme.exception.ThemeErrorCode +import roomescape.theme.exception.ThemeException +import roomescape.theme.implement.ThemeFinder +import roomescape.time.exception.TimeErrorCode +import roomescape.time.exception.TimeException +import roomescape.time.infrastructure.persistence.TimeRepository +import roomescape.util.TimeFixture +import java.time.LocalDate +import java.time.LocalTime + +class TimeFinderTest : FunSpec({ + val timeRepository: TimeRepository = mockk() + val reservationFinder: ReservationFinder = mockk() + val themeFinder: ThemeFinder = mockk() + + val timeFinder = TimeFinder(timeRepository, reservationFinder, themeFinder) + + context("findAll") { + test("모든 시간을 조회한다.") { + every { + timeRepository.findAll() + } returns listOf(mockk(), mockk(), mockk()) + + timeRepository.findAll() shouldHaveSize 3 + } + } + + context("findById") { + val timeId = 1L + test("동일한 ID인 시간을 찾아 응답한다.") { + every { + timeRepository.findByIdOrNull(timeId) + } returns mockk() + + timeFinder.findById(timeId) + + verify(exactly = 1) { + timeRepository.findByIdOrNull(timeId) + } + } + + test("동일한 ID인 시간이 없으면 실패한다.") { + every { + timeRepository.findByIdOrNull(timeId) + } returns null + + shouldThrow { + timeFinder.findById(timeId) + }.also { + it.errorCode shouldBe TimeErrorCode.TIME_NOT_FOUND + } + } + } + + context("findAllWithAvailabilityByDateAndThemeId") { + val date = LocalDate.now() + val themeId = 1L + + test("테마를 찾을 수 없으면 실패한다.") { + every { + themeFinder.findById(themeId) + } throws ThemeException(ThemeErrorCode.THEME_NOT_FOUND) + + shouldThrow { + timeFinder.findAllWithAvailabilityByDateAndThemeId(date, themeId) + }.also { + it.errorCode shouldBe ThemeErrorCode.THEME_NOT_FOUND + } + } + + test("날짜, 테마에 맞는 예약 자체가 없으면 모든 시간이 예약 가능하다.") { + every { + themeFinder.findById(themeId) + } returns mockk() + + every { + reservationFinder.findAllByDateAndTheme(date, any()) + } returns emptyList() + + every { + timeRepository.findAll() + } returns listOf( + TimeFixture.create(startAt = LocalTime.now()), + TimeFixture.create(startAt = LocalTime.now().plusMinutes(30)) + ) + + val result: List = + timeFinder.findAllWithAvailabilityByDateAndThemeId(date, themeId) + + assertSoftly(result) { + it shouldHaveSize 2 + it.all { time -> time.canReserve() } + } + } + } +}) diff --git a/src/test/kotlin/roomescape/time/implement/TimeValidatorTest.kt b/src/test/kotlin/roomescape/time/implement/TimeValidatorTest.kt new file mode 100644 index 00000000..ff835923 --- /dev/null +++ b/src/test/kotlin/roomescape/time/implement/TimeValidatorTest.kt @@ -0,0 +1,74 @@ +package roomescape.time.implement + +import io.kotest.assertions.throwables.shouldNotThrow +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import roomescape.reservation.implement.ReservationFinder +import roomescape.time.exception.TimeErrorCode +import roomescape.time.exception.TimeException +import roomescape.time.infrastructure.persistence.TimeEntity +import roomescape.time.infrastructure.persistence.TimeRepository +import roomescape.util.TimeFixture +import java.time.LocalTime + +class TimeValidatorTest : FunSpec({ + val timeRepository: TimeRepository = mockk() + val reservationFinder: ReservationFinder = mockk() + + val timeValidator = TimeValidator(timeRepository, reservationFinder) + + context("validateIsAlreadyExists") { + val startAt = LocalTime.now() + + test("같은 이메일을 가진 회원이 있으면 예외를 던진다.") { + every { + timeRepository.existsByStartAt(startAt) + } returns true + + shouldThrow { + timeValidator.validateIsAlreadyExists(startAt) + }.also { + it.errorCode shouldBe TimeErrorCode.TIME_DUPLICATED + } + } + + test("같은 이메일을 가진 회원이 없으면 종료한다.") { + every { + timeRepository.existsByStartAt(startAt) + } returns false + + shouldNotThrow { + timeValidator.validateIsAlreadyExists(startAt) + } + } + } + + context("validateIsReserved") { + val time: TimeEntity = TimeFixture.create(startAt = LocalTime.now()) + + test("해당 시간에 예약이 있으면 예외를 던진다.") { + every { + reservationFinder.isTimeReserved(time) + } returns true + + shouldThrow { + timeValidator.validateIsReserved(time) + }.also { + it.errorCode shouldBe TimeErrorCode.TIME_ALREADY_RESERVED + } + } + + test("해당 시간에 예약이 없으면 종료한다.") { + every { + reservationFinder.isTimeReserved(time) + } returns false + + shouldNotThrow { + timeValidator.validateIsReserved(time) + } + } + } +}) diff --git a/src/test/kotlin/roomescape/time/implement/TimeWriterTest.kt b/src/test/kotlin/roomescape/time/implement/TimeWriterTest.kt new file mode 100644 index 00000000..017e7047 --- /dev/null +++ b/src/test/kotlin/roomescape/time/implement/TimeWriterTest.kt @@ -0,0 +1,84 @@ +package roomescape.time.implement + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.mockk.* +import roomescape.time.exception.TimeErrorCode +import roomescape.time.exception.TimeException +import roomescape.time.infrastructure.persistence.TimeEntity +import roomescape.time.infrastructure.persistence.TimeRepository +import roomescape.util.TimeFixture +import roomescape.util.TsidFactory +import java.time.LocalTime + +class TimeWriterTest : FunSpec({ + + val timeValidator: TimeValidator = mockk() + val timeRepository: TimeRepository = mockk() + + val timeWriter = TimeWriter(timeValidator, timeRepository, TsidFactory) + + context("create") { + val startAt = LocalTime.now() + + test("중복된 시간이 있으면 실패한다.") { + every { + timeValidator.validateIsAlreadyExists(startAt) + } throws TimeException(TimeErrorCode.TIME_DUPLICATED) + + shouldThrow { + timeWriter.create(startAt) + }.also { + it.errorCode shouldBe TimeErrorCode.TIME_DUPLICATED + } + } + + test("중복된 시간이 없으면 저장한다.") { + every { + timeValidator.validateIsAlreadyExists(startAt) + } just Runs + + every { + timeRepository.save(any()) + } returns TimeFixture.create(startAt = startAt) + + timeWriter.create(startAt) + + verify(exactly = 1) { + timeRepository.save(any()) + } + } + } + + context("delete") { + val time: TimeEntity = TimeFixture.create() + test("예약이 있는 시간이면 실패한다.") { + every { + timeValidator.validateIsReserved(time) + } throws TimeException(TimeErrorCode.TIME_ALREADY_RESERVED) + + shouldThrow { + timeWriter.delete(time) + }.also { + it.errorCode shouldBe TimeErrorCode.TIME_ALREADY_RESERVED + } + } + + test("예약이 없는 시간이면 제거한다.") { + every { + timeValidator.validateIsReserved(time) + } just Runs + + every { + timeRepository.delete(time) + } just Runs + + timeWriter.delete(time) + + verify(exactly = 1) { + timeRepository.delete(time) + } + } + } +}) diff --git a/src/test/kotlin/roomescape/time/web/TimeControllerTest.kt b/src/test/kotlin/roomescape/time/web/TimeControllerTest.kt index c5a57e44..2d4c9ebf 100644 --- a/src/test/kotlin/roomescape/time/web/TimeControllerTest.kt +++ b/src/test/kotlin/roomescape/time/web/TimeControllerTest.kt @@ -1,28 +1,21 @@ package roomescape.time.web import com.ninjasquad.springmockk.MockkBean -import com.ninjasquad.springmockk.SpykBean import io.kotest.assertions.assertSoftly import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe import io.mockk.every import org.hamcrest.Matchers.equalTo import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest -import org.springframework.context.annotation.Import -import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext -import org.springframework.data.repository.findByIdOrNull import org.springframework.http.MediaType import org.springframework.test.web.servlet.MockMvc import roomescape.auth.exception.AuthErrorCode -import roomescape.common.config.JacksonConfig -import roomescape.reservation.infrastructure.persistence.ReservationRepository +import roomescape.theme.exception.ThemeErrorCode +import roomescape.theme.exception.ThemeException import roomescape.time.business.TimeService import roomescape.time.exception.TimeErrorCode -import roomescape.time.infrastructure.persistence.TimeEntity -import roomescape.time.infrastructure.persistence.TimeRepository -import roomescape.util.ReservationFixture +import roomescape.time.exception.TimeException import roomescape.util.RoomescapeApiTest -import roomescape.util.ThemeFixture import roomescape.util.TimeFixture import java.time.LocalDate import java.time.LocalTime @@ -32,15 +25,9 @@ class TimeControllerTest( val mockMvc: MockMvc, ) : RoomescapeApiTest() { - @SpykBean + @MockkBean private lateinit var timeService: TimeService - @MockkBean - private lateinit var timeRepository: TimeRepository - - @MockkBean - private lateinit var reservationRepository: ReservationRepository - init { Given("등록된 모든 시간을 조회할 때") { val endpoint = "/times" @@ -52,11 +39,11 @@ class TimeControllerTest( Then("정상 응답") { every { - timeRepository.findAll() + timeService.findTimes() } returns listOf( TimeFixture.create(id = 1L), TimeFixture.create(id = 2L) - ) + ).toResponse() runGetTest( mockMvc = mockMvc, @@ -76,7 +63,7 @@ class TimeControllerTest( loginAsUser() val expectedError = AuthErrorCode.ACCESS_DENIED - Then("에러 응답을 받는다.") { + Then("예외 응답") { runGetTest( mockMvc = mockMvc, endpoint = endpoint, @@ -85,7 +72,7 @@ class TimeControllerTest( }.andExpect { content { contentType(MediaType.APPLICATION_JSON) - jsonPath("$.code") { value(expectedError.errorCode) } + jsonPath("$.code") { equalTo(expectedError.errorCode) } } } } @@ -139,8 +126,8 @@ class TimeControllerTest( Then("동일한 시간이 존재하면 예외 응답") { val expectedError = TimeErrorCode.TIME_DUPLICATED every { - timeRepository.existsByStartAt(time) - } returns true + timeService.createTime(request) + } throws TimeException(expectedError) runPostTest( mockMvc = mockMvc, @@ -150,7 +137,7 @@ class TimeControllerTest( status { isEqualTo(expectedError.httpStatus.value()) } content { contentType(MediaType.APPLICATION_JSON) - jsonPath("$.code") { value(expectedError.errorCode) } + jsonPath("$.code") { equalTo(expectedError.errorCode) } } } } @@ -159,7 +146,7 @@ class TimeControllerTest( When("관리자가 아닌 경우") { loginAsUser() - Then("에러 응답을 받는다.") { + Then("예외 응답") { val expectedError = AuthErrorCode.ACCESS_DENIED runPostTest( mockMvc = mockMvc, @@ -197,9 +184,10 @@ class TimeControllerTest( Then("없는 시간을 조회하면 예외 응답") { val id = 1L val expectedError = TimeErrorCode.TIME_NOT_FOUND + every { - timeRepository.findByIdOrNull(id) - } returns null + timeService.deleteTime(id) + } throws TimeException(expectedError) runDeleteTest( mockMvc = mockMvc, @@ -208,7 +196,7 @@ class TimeControllerTest( status { isEqualTo(expectedError.httpStatus.value()) } content { contentType(MediaType.APPLICATION_JSON) - jsonPath("$.code") { value(expectedError.errorCode) } + jsonPath("$.code") { equalTo(expectedError.errorCode) } } } } @@ -216,13 +204,10 @@ class TimeControllerTest( Then("예약이 있는 시간을 삭제하면 예외 응답") { val id = 1L val expectedError = TimeErrorCode.TIME_ALREADY_RESERVED - every { - timeRepository.findByIdOrNull(id) - } returns TimeFixture.create(id = id) every { - reservationRepository.findAllByTime(any()) - } returns listOf(ReservationFixture.create()) + timeService.deleteTime(id) + } throws TimeException(expectedError) runDeleteTest( mockMvc = mockMvc, @@ -231,7 +216,7 @@ class TimeControllerTest( status { isEqualTo(expectedError.httpStatus.value()) } content { contentType(MediaType.APPLICATION_JSON) - jsonPath("$.code") { value(expectedError.errorCode) } + jsonPath("$.code") { equalTo(expectedError.errorCode) } } } } @@ -240,7 +225,7 @@ class TimeControllerTest( When("관리자가 아닌 경우") { loginAsUser() - Then("에러 응답을 받는다.") { + Then("예외 응답") { val expectedError = AuthErrorCode.ACCESS_DENIED runDeleteTest( mockMvc = mockMvc, @@ -254,35 +239,34 @@ class TimeControllerTest( } Given("날짜, 테마가 주어졌을 때") { - loginAsUser() - + beforeTest { + loginAsUser() + } val date: LocalDate = LocalDate.now() val themeId = 1L - When("저장된 예약 시간이 있으면") { - val times: List = listOf( - TimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)), - TimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0)) - ) - - every { - timeRepository.findAll() - } returns times - - Then("그 시간과, 해당 날짜와 테마에 대한 예약 여부가 담긴 목록을 응답") { - - every { - reservationRepository.findByDateAndThemeId(date, themeId) - } returns listOf( - ReservationFixture.create( - id = 1L, - date = date, - theme = ThemeFixture.create(id = themeId), - time = times[0] + When("테마를 찾을 수 있으면") { + Then("해당 날짜와 테마에 대한 예약 여부가 담긴 모든 시간을 응답") { + val response = TimeWithAvailabilityListResponse( + listOf( + TimeWithAvailabilityResponse(id = 1L, startAt = LocalTime.now(), isAvailable = true), + TimeWithAvailabilityResponse( + id = 2L, + startAt = LocalTime.now().plusMinutes(30), + isAvailable = false + ), + TimeWithAvailabilityResponse( + id = 1L, + startAt = LocalTime.now().plusHours(1), + isAvailable = true + ), ) ) + every { + timeService.findTimesWithAvailability(date, themeId) + } returns response - val response = runGetTest( + val result = runGetTest( mockMvc = mockMvc, endpoint = "/times/search?date=$date&themeId=$themeId", ) { @@ -292,16 +276,33 @@ class TimeControllerTest( } }.andReturn().readValue(TimeWithAvailabilityListResponse::class.java) - assertSoftly(response.times) { - this shouldHaveSize times.size - this[0].id shouldBe times[0].id - this[0].isAvailable shouldBe false + assertSoftly(result.times) { + this shouldHaveSize response.times.size + this[0].id shouldBe response.times[0].id + this[0].isAvailable shouldBe response.times[0].isAvailable - this[1].id shouldBe times[1].id - this[1].isAvailable shouldBe true + this[1].id shouldBe response.times[1].id + this[1].isAvailable shouldBe response.times[1].isAvailable + } + } + } + + When("테마를 찾을 수 없으면") { + val expectedError = ThemeErrorCode.THEME_NOT_FOUND + every { + timeService.findTimesWithAvailability(date, themeId) + } throws ThemeException(expectedError) + + Then("예외 응답") { + runGetTest( + mockMvc = mockMvc, + endpoint = "/times/search?date=$date&themeId=$themeId", + ) { + status { isNotFound() } + jsonPath("$.code", equalTo(expectedError.errorCode)) } } } } } -} \ No newline at end of file +}