test: 엔티티 변경 사항 테스트 반영

This commit is contained in:
이상진 2025-08-02 15:56:31 +09:00
parent c0c2ef21c6
commit 612bbfbddc
17 changed files with 259 additions and 289 deletions

View File

@ -15,10 +15,11 @@ import roomescape.member.infrastructure.persistence.MemberEntity
import roomescape.member.infrastructure.persistence.MemberRepository import roomescape.member.infrastructure.persistence.MemberRepository
import roomescape.util.JwtFixture import roomescape.util.JwtFixture
import roomescape.util.MemberFixture import roomescape.util.MemberFixture
import roomescape.util.TsidFactory
class AuthServiceTest : BehaviorSpec({ class AuthServiceTest : BehaviorSpec({
val memberRepository: MemberRepository = mockk() val memberRepository: MemberRepository = mockk()
val memberService: MemberService = MemberService(memberRepository) val memberService = MemberService(TsidFactory, memberRepository)
val jwtHandler: JwtHandler = JwtFixture.create() val jwtHandler: JwtHandler = JwtFixture.create()
val authService = AuthService(memberService, jwtHandler) val authService = AuthService(memberService, jwtHandler)

View File

@ -14,13 +14,14 @@ import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
import roomescape.payment.infrastructure.persistence.PaymentRepository import roomescape.payment.infrastructure.persistence.PaymentRepository
import roomescape.payment.web.PaymentCancelRequest import roomescape.payment.web.PaymentCancelRequest
import roomescape.util.PaymentFixture import roomescape.util.PaymentFixture
import roomescape.util.TsidFactory
import java.time.OffsetDateTime import java.time.OffsetDateTime
class PaymentServiceTest : FunSpec({ class PaymentServiceTest : FunSpec({
val paymentRepository: PaymentRepository = mockk() val paymentRepository: PaymentRepository = mockk()
val canceledPaymentRepository: CanceledPaymentRepository = mockk() val canceledPaymentRepository: CanceledPaymentRepository = mockk()
val paymentService = PaymentService(paymentRepository, canceledPaymentRepository) val paymentService = PaymentService(TsidFactory, paymentRepository, canceledPaymentRepository)
context("createCanceledPaymentByReservationId") { context("createCanceledPaymentByReservationId") {
val reservationId = 1L val reservationId = 1L

View File

@ -1,11 +1,13 @@
package roomescape.payment.infrastructure.client package roomescape.payment.infrastructure.client
import com.ninjasquad.springmockk.MockkBean
import io.kotest.assertions.assertSoftly import io.kotest.assertions.assertSoftly
import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest import org.springframework.boot.test.autoconfigure.web.client.RestClientTest
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext
import org.springframework.http.HttpMethod import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.MediaType import org.springframework.http.MediaType
@ -20,6 +22,7 @@ import roomescape.payment.web.PaymentCancelRequest
import roomescape.payment.web.PaymentCancelResponse import roomescape.payment.web.PaymentCancelResponse
@RestClientTest(TossPaymentClient::class) @RestClientTest(TossPaymentClient::class)
@MockkBean(JpaMetamodelMappingContext::class)
class TossPaymentClientTest( class TossPaymentClientTest(
@Autowired val client: TossPaymentClient, @Autowired val client: TossPaymentClient,
@Autowired val mockServer: MockRestServiceServer @Autowired val mockServer: MockRestServiceServer

View File

@ -5,10 +5,12 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import roomescape.common.config.next
import roomescape.util.PaymentFixture import roomescape.util.PaymentFixture
import roomescape.util.TsidFactory
import java.util.* import java.util.*
@DataJpaTest @DataJpaTest(showSql = false)
class CanceledPaymentRepositoryTest( class CanceledPaymentRepositoryTest(
@Autowired val canceledPaymentRepository: CanceledPaymentRepository, @Autowired val canceledPaymentRepository: CanceledPaymentRepository,
) : FunSpec() { ) : FunSpec() {
@ -16,7 +18,7 @@ class CanceledPaymentRepositoryTest(
context("paymentKey로 CanceledPaymentEntity 조회") { context("paymentKey로 CanceledPaymentEntity 조회") {
val paymentKey = "test-payment-key" val paymentKey = "test-payment-key"
beforeTest { beforeTest {
PaymentFixture.createCanceled(paymentKey = paymentKey) PaymentFixture.createCanceled(id = TsidFactory.next(), paymentKey = paymentKey)
.also { canceledPaymentRepository.save(it) } .also { canceledPaymentRepository.save(it) }
} }

View File

@ -6,11 +6,13 @@ import io.kotest.matchers.shouldBe
import jakarta.persistence.EntityManager import jakarta.persistence.EntityManager
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import roomescape.common.config.next
import roomescape.reservation.infrastructure.persistence.ReservationEntity import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.util.PaymentFixture import roomescape.util.PaymentFixture
import roomescape.util.ReservationFixture import roomescape.util.ReservationFixture
import roomescape.util.TsidFactory
@DataJpaTest @DataJpaTest(showSql = false)
class PaymentRepositoryTest( class PaymentRepositoryTest(
@Autowired val paymentRepository: PaymentRepository, @Autowired val paymentRepository: PaymentRepository,
@Autowired val entityManager: EntityManager @Autowired val entityManager: EntityManager
@ -91,7 +93,9 @@ class PaymentRepositoryTest(
} }
private fun setupReservation(): ReservationEntity { private fun setupReservation(): ReservationEntity {
return ReservationFixture.create().also { return ReservationFixture.create(
id = TsidFactory.next()
).also {
entityManager.persist(it.member) entityManager.persist(it.member)
entityManager.persist(it.theme) entityManager.persist(it.theme)
entityManager.persist(it.time) entityManager.persist(it.time)

View File

@ -16,6 +16,7 @@ import roomescape.theme.business.ThemeService
import roomescape.time.business.TimeService import roomescape.time.business.TimeService
import roomescape.util.MemberFixture import roomescape.util.MemberFixture
import roomescape.util.ReservationFixture import roomescape.util.ReservationFixture
import roomescape.util.TsidFactory
import roomescape.util.TimeFixture import roomescape.util.TimeFixture
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
@ -27,6 +28,7 @@ class ReservationServiceTest : FunSpec({
val memberService: MemberService = mockk() val memberService: MemberService = mockk()
val themeService: ThemeService = mockk() val themeService: ThemeService = mockk()
val reservationService = ReservationService( val reservationService = ReservationService(
TsidFactory,
reservationRepository, reservationRepository,
timeService, timeService,
memberService, memberService,

View File

@ -15,7 +15,7 @@ import roomescape.util.ReservationFixture
import roomescape.util.ThemeFixture import roomescape.util.ThemeFixture
import roomescape.util.TimeFixture import roomescape.util.TimeFixture
@DataJpaTest @DataJpaTest(showSql = false)
class ReservationRepositoryTest( class ReservationRepositoryTest(
val entityManager: EntityManager, val entityManager: EntityManager,
val reservationRepository: ReservationRepository, val reservationRepository: ReservationRepository,

View File

@ -15,7 +15,7 @@ import roomescape.util.ThemeFixture
import roomescape.util.TimeFixture import roomescape.util.TimeFixture
import java.time.LocalDate import java.time.LocalDate
@DataJpaTest @DataJpaTest(showSql = false)
class ReservationSearchSpecificationTest( class ReservationSearchSpecificationTest(
val entityManager: EntityManager, val entityManager: EntityManager,
val reservationRepository: ReservationRepository val reservationRepository: ReservationRepository

View File

@ -39,7 +39,7 @@ import java.time.LocalTime
class ReservationControllerTest( class ReservationControllerTest(
@LocalServerPort val port: Int, @LocalServerPort val port: Int,
val entityManager: EntityManager, val entityManager: EntityManager,
val transactionTemplate: TransactionTemplate val transactionTemplate: TransactionTemplate,
) : FunSpec({ ) : FunSpec({
extension(DatabaseCleanerExtension(mode = CleanerMode.AFTER_EACH_TEST)) extension(DatabaseCleanerExtension(mode = CleanerMode.AFTER_EACH_TEST))
}) { }) {
@ -55,24 +55,34 @@ class ReservationControllerTest(
@MockkBean @MockkBean
lateinit var jwtHandler: JwtHandler lateinit var jwtHandler: JwtHandler
lateinit var testDataHelper: TestDataHelper
fun login(member: MemberEntity) {
every { jwtHandler.getMemberIdFromToken(any()) } returns member.id!!
every { memberService.findById(member.id!!) } returns member
every { memberIdResolver.resolveArgument(any(), any(), any(), any()) } returns member.id!!
}
init { init {
beforeSpec {
testDataHelper = TestDataHelper(entityManager, transactionTemplate)
}
context("POST /reservations") { context("POST /reservations") {
lateinit var member: MemberEntity
beforeTest { beforeTest {
member = login(MemberFixture.create(role = Role.MEMBER)) val member = testDataHelper.createMember(role = Role.MEMBER)
login(member)
} }
test("정상 응답") { test("정상 응답") {
val reservationRequest = createRequest() val reservationRequest = testDataHelper.createReservationRequest()
val paymentApproveResponse = PaymentFixture.createApproveResponse().copy( val paymentApproveResponse = PaymentFixture.createApproveResponse().copy(
paymentKey = reservationRequest.paymentKey, paymentKey = reservationRequest.paymentKey,
orderId = reservationRequest.orderId, orderId = reservationRequest.orderId,
totalAmount = reservationRequest.amount, totalAmount = reservationRequest.amount,
) )
every { every { paymentClient.confirm(any()) } returns paymentApproveResponse
paymentClient.confirm(any())
} returns paymentApproveResponse
Given { Given {
port(port) port(port)
@ -88,12 +98,10 @@ class ReservationControllerTest(
} }
test("결제 과정에서 발생하는 에러는 그대로 응답") { test("결제 과정에서 발생하는 에러는 그대로 응답") {
val reservationRequest = createRequest() val reservationRequest = testDataHelper.createReservationRequest()
val paymentException = PaymentException(PaymentErrorCode.PAYMENT_PROVIDER_ERROR) val paymentException = PaymentException(PaymentErrorCode.PAYMENT_PROVIDER_ERROR)
every { every { paymentClient.confirm(any()) } throws paymentException
paymentClient.confirm(any())
} throws paymentException
Given { Given {
port(port) port(port)
@ -108,24 +116,20 @@ class ReservationControllerTest(
} }
test("결제 완료 후 예약 / 결제 정보 저장 과정에서 에러 발생시 결제 취소 후 에러 응답을 받는다.") { test("결제 완료 후 예약 / 결제 정보 저장 과정에서 에러 발생시 결제 취소 후 에러 응답을 받는다.") {
val reservationRequest = createRequest() val reservationRequest = testDataHelper.createReservationRequest()
val paymentApproveResponse = PaymentFixture.createApproveResponse().copy( val paymentApproveResponse = PaymentFixture.createApproveResponse().copy(
paymentKey = reservationRequest.paymentKey, paymentKey = reservationRequest.paymentKey,
orderId = reservationRequest.orderId, orderId = reservationRequest.orderId,
totalAmount = reservationRequest.amount, totalAmount = reservationRequest.amount,
) )
every { every { paymentClient.confirm(any()) } returns paymentApproveResponse
paymentClient.confirm(any())
} returns paymentApproveResponse
// 예약 저장 과정에서 테마가 없는 예외 // 예약 저장 과정에서 테마가 없는 예외
val invalidRequest = reservationRequest.copy(themeId = reservationRequest.themeId + 1) val invalidRequest = reservationRequest.copy(themeId = reservationRequest.themeId + 1)
val expectedException = ThemeErrorCode.THEME_NOT_FOUND val expectedException = ThemeErrorCode.THEME_NOT_FOUND
every { every { paymentClient.cancel(any()) } returns PaymentFixture.createCancelResponse()
paymentClient.cancel(any())
} returns PaymentFixture.createCancelResponse()
val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery( val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery(
"SELECT COUNT(c) FROM CanceledPaymentEntity c", "SELECT COUNT(c) FROM CanceledPaymentEntity c",
@ -153,13 +157,13 @@ class ReservationControllerTest(
} }
context("GET /reservations") { context("GET /reservations") {
lateinit var reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>> lateinit var reservations: Map<MemberEntity, List<ReservationEntity>>
beforeTest { beforeTest {
reservations = createDummyReservations() reservations = testDataHelper.createDummyReservations()
} }
test("관리자이면 정상 응답") { test("관리자이면 정상 응답") {
login(MemberFixture.create(role = Role.ADMIN)) login(testDataHelper.createMember(role = Role.ADMIN))
Given { Given {
port(port) port(port)
contentType(MediaType.APPLICATION_JSON_VALUE) contentType(MediaType.APPLICATION_JSON_VALUE)
@ -173,13 +177,14 @@ class ReservationControllerTest(
} }
context("GET /reservations-mine") { context("GET /reservations-mine") {
lateinit var reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>> lateinit var reservations: Map<MemberEntity, List<ReservationEntity>>
beforeTest { beforeTest {
reservations = createDummyReservations() reservations = testDataHelper.createDummyReservations()
} }
test("로그인한 회원이 자신의 예약 목록을 조회한다.") { test("로그인한 회원이 자신의 예약 목록을 조회한다.") {
val member: MemberEntity = login(reservations.keys.first()) val member = reservations.keys.first()
login(member)
val expectedReservations: Int = reservations[member]?.size ?: 0 val expectedReservations: Int = reservations[member]?.size ?: 0
Given { Given {
@ -195,9 +200,9 @@ class ReservationControllerTest(
} }
context("GET /reservations/search") { context("GET /reservations/search") {
lateinit var reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>> lateinit var reservations: Map<MemberEntity, List<ReservationEntity>>
beforeTest { beforeTest {
reservations = createDummyReservations() reservations = testDataHelper.createDummyReservations()
} }
test("관리자만 검색할 수 있다.") { test("관리자만 검색할 수 있다.") {
@ -216,7 +221,7 @@ class ReservationControllerTest(
} }
test("파라미터를 지정하지 않으면 전체 목록 응답") { test("파라미터를 지정하지 않으면 전체 목록 응답") {
login(MemberFixture.create(role = Role.ADMIN)) login(testDataHelper.createMember(role = Role.ADMIN))
Given { Given {
port(port) port(port)
@ -230,7 +235,7 @@ class ReservationControllerTest(
} }
test("시작 날짜가 종료 날짜 이전이면 예외 응답") { test("시작 날짜가 종료 날짜 이전이면 예외 응답") {
login(MemberFixture.create(role = Role.ADMIN)) login(testDataHelper.createMember(role = Role.ADMIN))
val startDate = LocalDate.now().plusDays(1) val startDate = LocalDate.now().plusDays(1)
val endDate = LocalDate.now() val endDate = LocalDate.now()
@ -250,8 +255,8 @@ class ReservationControllerTest(
} }
test("동일한 회원의 모든 예약 응답") { test("동일한 회원의 모든 예약 응답") {
login(MemberFixture.create(role = Role.ADMIN)) login(testDataHelper.createMember(role = Role.ADMIN))
val member: MemberEntity = reservations.keys.first() val member = reservations.keys.first()
Given { Given {
port(port) port(port)
@ -266,7 +271,7 @@ class ReservationControllerTest(
} }
test("동일한 테마의 모든 예약 응답") { test("동일한 테마의 모든 예약 응답") {
login(MemberFixture.create(role = Role.ADMIN)) login(testDataHelper.createMember(role = Role.ADMIN))
val themes = reservations.values.flatten().map { it.theme } val themes = reservations.values.flatten().map { it.theme }
val requestThemeId: Long = themes.first().id!! val requestThemeId: Long = themes.first().id!!
@ -278,12 +283,12 @@ class ReservationControllerTest(
get("/reservations/search") get("/reservations/search")
}.Then { }.Then {
statusCode(200) statusCode(200)
body("data.reservations.size()", equalTo(themes.filter { it.id == requestThemeId }.size)) body("data.reservations.size()", equalTo(themes.count { it.id == requestThemeId }))
} }
} }
test("시작 날짜와 종료 날짜 사이의 예약 응답") { test("시작 날짜와 종료 날짜 사이의 예약 응답") {
login(MemberFixture.create(role = Role.ADMIN)) login(testDataHelper.createMember(role = Role.ADMIN))
val dateFrom: LocalDate = reservations.values.flatten().minOf { it.date } val dateFrom: LocalDate = reservations.values.flatten().minOf { it.date }
val dateTo: LocalDate = reservations.values.flatten().maxOf { it.date } val dateTo: LocalDate = reservations.values.flatten().maxOf { it.date }
@ -302,14 +307,14 @@ class ReservationControllerTest(
} }
context("DELETE /reservations/{id}") { context("DELETE /reservations/{id}") {
lateinit var reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>> lateinit var reservations: Map<MemberEntity, List<ReservationEntity>>
beforeTest { beforeTest {
reservations = createDummyReservations() reservations = testDataHelper.createDummyReservations()
} }
test("관리자만 예약을 삭제할 수 있다.") { test("관리자만 예약을 삭제할 수 있다.") {
login(MemberFixture.create(role = Role.MEMBER)) login(testDataHelper.createMember(role = Role.MEMBER))
val reservation: ReservationEntity = reservations.values.flatten().first() val reservation = reservations.values.flatten().first()
val expectedError = AuthErrorCode.ACCESS_DENIED val expectedError = AuthErrorCode.ACCESS_DENIED
Given { Given {
@ -323,18 +328,12 @@ class ReservationControllerTest(
} }
test("결제되지 않은 예약은 바로 제거") { test("결제되지 않은 예약은 바로 제거") {
login(MemberFixture.create(role = Role.ADMIN)) login(testDataHelper.createMember(role = Role.ADMIN))
val reservationId: Long = reservations.values.flatten().first().id!! val reservationId = reservations.values.flatten().first().id!!
transactionTemplate.execute { transactionTemplate.executeWithoutResult {
val reservation: ReservationEntity = entityManager.find( val reservation = entityManager.find(ReservationEntity::class.java, reservationId)
ReservationEntity::class.java,
reservationId
)
reservation.status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED reservation.status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
entityManager.persist(reservation)
entityManager.flush()
entityManager.clear()
} }
Given { Given {
@ -345,32 +344,18 @@ class ReservationControllerTest(
statusCode(HttpStatus.NO_CONTENT.value()) statusCode(HttpStatus.NO_CONTENT.value())
} }
// 예약이 삭제되었는지 확인 val deletedReservation = transactionTemplate.execute {
transactionTemplate.executeWithoutResult { entityManager.find(ReservationEntity::class.java, reservationId)
val deletedReservation = entityManager.find(
ReservationEntity::class.java,
reservationId
)
deletedReservation shouldBe null
} }
deletedReservation shouldBe null
} }
test("결제된 예약은 취소 후 제거") { test("결제된 예약은 취소 후 제거") {
login(MemberFixture.create(role = Role.ADMIN)) login(testDataHelper.createMember(role = Role.ADMIN))
val reservation: ReservationEntity = reservations.values.flatten().first() val reservation = reservations.values.flatten().first { it.status == ReservationStatus.CONFIRMED }
lateinit var payment: PaymentEntity testDataHelper.createPayment(reservation)
transactionTemplate.execute { every { paymentClient.cancel(any()) } returns PaymentFixture.createCancelResponse()
payment = PaymentFixture.create(reservation = reservation).also {
entityManager.persist(it)
entityManager.flush()
entityManager.clear()
}
}
every {
paymentClient.cancel(any())
} returns PaymentFixture.createCancelResponse()
val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery( val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery(
"SELECT COUNT(c) FROM CanceledPaymentEntity c", "SELECT COUNT(c) FROM CanceledPaymentEntity c",
@ -396,15 +381,17 @@ class ReservationControllerTest(
context("POST /reservations/admin") { context("POST /reservations/admin") {
test("관리자가 예약을 추가하면 결제 대기 상태로 예약 생성") { test("관리자가 예약을 추가하면 결제 대기 상태로 예약 생성") {
val member = login(MemberFixture.create(role = Role.ADMIN)) val admin = testDataHelper.createMember(role = Role.ADMIN)
val adminRequest: AdminReservationCreateRequest = createRequest().let { login(admin)
AdminReservationCreateRequest( val theme = testDataHelper.createTheme()
date = it.date, val time = testDataHelper.createTime()
themeId = it.themeId,
timeId = it.timeId, val adminRequest = AdminReservationCreateRequest(
memberId = member.id!!, date = LocalDate.now().plusDays(1),
) themeId = theme.id!!,
} timeId = time.id!!,
memberId = admin.id!!,
)
Given { Given {
port(port) port(port)
@ -420,13 +407,13 @@ class ReservationControllerTest(
} }
context("GET /reservations/waiting") { context("GET /reservations/waiting") {
lateinit var reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>> lateinit var reservations: Map<MemberEntity, List<ReservationEntity>>
beforeTest { beforeTest {
reservations = createDummyReservations() reservations = testDataHelper.createDummyReservations(reservationCount = 5)
} }
test("관리자가 아니면 조회할 수 없다.") { test("관리자가 아니면 조회할 수 없다.") {
login(MemberFixture.create(role = Role.MEMBER)) login(testDataHelper.createMember(role = Role.MEMBER))
val expectedError = AuthErrorCode.ACCESS_DENIED val expectedError = AuthErrorCode.ACCESS_DENIED
Given { Given {
@ -441,7 +428,7 @@ class ReservationControllerTest(
} }
test("대기 중인 예약 목록을 조회한다.") { test("대기 중인 예약 목록을 조회한다.") {
login(MemberFixture.create(role = Role.ADMIN)) login(testDataHelper.createMember(role = Role.ADMIN))
val expected = reservations.values.flatten() val expected = reservations.values.flatten()
.count { it.status == ReservationStatus.WAITING } .count { it.status == ReservationStatus.WAITING }
@ -459,14 +446,16 @@ class ReservationControllerTest(
context("POST /reservations/waiting") { context("POST /reservations/waiting") {
test("회원이 대기 예약을 추가한다.") { test("회원이 대기 예약을 추가한다.") {
val member = login(MemberFixture.create(role = Role.MEMBER)) val member = testDataHelper.createMember(role = Role.MEMBER)
val waitingCreateRequest: WaitingCreateRequest = createRequest().let { login(member)
WaitingCreateRequest( val theme = testDataHelper.createTheme()
date = it.date, val time = testDataHelper.createTime()
themeId = it.themeId,
timeId = it.timeId val waitingCreateRequest = WaitingCreateRequest(
) date = LocalDate.now().plusDays(1),
} themeId = theme.id!!,
timeId = time.id!!
)
Given { Given {
port(port) port(port)
@ -476,33 +465,30 @@ class ReservationControllerTest(
post("/reservations/waiting") post("/reservations/waiting")
}.Then { }.Then {
statusCode(201) statusCode(201)
body("data.member.id", equalTo(member.id!!.toInt())) body("data.member.id", equalTo(member.id!!))
body("data.status", equalTo(ReservationStatus.WAITING.name)) body("data.status", equalTo(ReservationStatus.WAITING.name))
} }
} }
test("이미 예약된 시간, 테마로 대기 예약 요청 시 예외 응답") { test("이미 예약된 시간, 테마로 대기 예약 요청 시 예외 응답") {
val member = login(MemberFixture.create(role = Role.MEMBER)) val member = testDataHelper.createMember(role = Role.MEMBER)
val reservationRequest = createRequest() login(member)
val theme = testDataHelper.createTheme()
val time = testDataHelper.createTime()
val date = LocalDate.now().plusDays(1)
transactionTemplate.executeWithoutResult { testDataHelper.createReservation(
val reservation = ReservationFixture.create( date = date,
date = reservationRequest.date, theme = theme,
theme = entityManager.find(ThemeEntity::class.java, reservationRequest.themeId), time = time,
time = entityManager.find(TimeEntity::class.java, reservationRequest.timeId), member = member,
member = member, status = ReservationStatus.CONFIRMED
status = ReservationStatus.WAITING )
)
entityManager.persist(reservation)
entityManager.flush()
entityManager.clear()
}
// 이미 예약된 시간, 테마로 대기 예약 요청
val waitingCreateRequest = WaitingCreateRequest( val waitingCreateRequest = WaitingCreateRequest(
date = reservationRequest.date, date = date,
themeId = reservationRequest.themeId, themeId = theme.id!!,
timeId = reservationRequest.timeId timeId = time.id!!
) )
val expectedError = ReservationErrorCode.ALREADY_RESERVE val expectedError = ReservationErrorCode.ALREADY_RESERVE
@ -520,14 +506,10 @@ class ReservationControllerTest(
} }
context("DELETE /reservations/waiting/{id}") { context("DELETE /reservations/waiting/{id}") {
lateinit var reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>>
beforeTest {
reservations = createDummyReservations()
}
test("대기 중인 예약을 취소한다.") { test("대기 중인 예약을 취소한다.") {
val member = login(MemberFixture.create(role = Role.MEMBER)) val member = testDataHelper.createMember(role = Role.MEMBER)
val waiting: ReservationEntity = createSingleReservation( login(member)
val waiting = testDataHelper.createReservation(
member = member, member = member,
status = ReservationStatus.WAITING status = ReservationStatus.WAITING
) )
@ -540,17 +522,16 @@ class ReservationControllerTest(
statusCode(HttpStatus.NO_CONTENT.value()) statusCode(HttpStatus.NO_CONTENT.value())
} }
transactionTemplate.executeWithoutResult { _ -> val deleted = transactionTemplate.execute {
entityManager.find( entityManager.find(ReservationEntity::class.java, waiting.id)
ReservationEntity::class.java,
waiting.id
) shouldBe null
} }
deleted shouldBe null
} }
test("이미 확정된 예약을 삭제하면 예외 응답") { test("이미 확정된 예약을 삭제하면 예외 응답") {
val member = login(MemberFixture.create(role = Role.MEMBER)) val member = testDataHelper.createMember(role = Role.MEMBER)
val reservation: ReservationEntity = createSingleReservation( login(member)
val reservation = testDataHelper.createReservation(
member = member, member = member,
status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
) )
@ -559,7 +540,7 @@ class ReservationControllerTest(
Given { Given {
port(port) port(port)
}.When { }.When {
delete("/reservations/waiting/{id}", reservation.id) delete("/reservations/waiting/${reservation.id}")
}.Then { }.Then {
statusCode(expectedError.httpStatus.value()) statusCode(expectedError.httpStatus.value())
body("code", equalTo(expectedError.errorCode)) body("code", equalTo(expectedError.errorCode))
@ -569,7 +550,7 @@ class ReservationControllerTest(
context("POST /reservations/waiting/{id}/confirm") { context("POST /reservations/waiting/{id}/confirm") {
test("관리자만 승인할 수 있다.") { test("관리자만 승인할 수 있다.") {
login(MemberFixture.create(role = Role.MEMBER)) login(testDataHelper.createMember(role = Role.MEMBER))
val expectedError = AuthErrorCode.ACCESS_DENIED val expectedError = AuthErrorCode.ACCESS_DENIED
Given { Given {
port(port) port(port)
@ -582,9 +563,8 @@ class ReservationControllerTest(
} }
test("대기 예약을 승인하면 결제 대기 상태로 변경") { test("대기 예약을 승인하면 결제 대기 상태로 변경") {
val member = login(MemberFixture.create(role = Role.ADMIN)) login(testDataHelper.createMember(role = Role.ADMIN))
val reservation = createSingleReservation( val reservation = testDataHelper.createReservation(
member = member,
status = ReservationStatus.WAITING status = ReservationStatus.WAITING
) )
@ -596,39 +576,28 @@ class ReservationControllerTest(
statusCode(200) statusCode(200)
} }
transactionTemplate.executeWithoutResult { _ -> val updatedReservation = transactionTemplate.execute {
entityManager.find( entityManager.find(ReservationEntity::class.java, reservation.id)
ReservationEntity::class.java,
reservation.id
)?.also {
it.status shouldBe ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
} ?: throw AssertionError("Reservation not found")
} }
updatedReservation?.status shouldBe ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
} }
test("다른 확정된 예약을 승인하면 예외 응답") { test("다른 확정된 예약을 승인하면 예외 응답") {
val admin = login(MemberFixture.create(role = Role.ADMIN)) val admin = testDataHelper.createMember(role = Role.ADMIN)
val alreadyReserved = createSingleReservation( login(admin)
val alreadyReserved = testDataHelper.createReservation(
member = admin, member = admin,
status = ReservationStatus.CONFIRMED status = ReservationStatus.CONFIRMED
) )
val member = MemberFixture.create(account = "account", role = Role.MEMBER).also { it -> val member = testDataHelper.createMember(role = Role.MEMBER)
transactionTemplate.executeWithoutResult { _ -> val waiting = testDataHelper.createReservation(
entityManager.persist(it)
}
}
val waiting = ReservationFixture.create(
date = alreadyReserved.date, date = alreadyReserved.date,
time = alreadyReserved.time, time = alreadyReserved.time,
theme = alreadyReserved.theme, theme = alreadyReserved.theme,
member = member, member = member,
status = ReservationStatus.WAITING status = ReservationStatus.WAITING
).also { )
transactionTemplate.executeWithoutResult { _ ->
entityManager.persist(it)
}
}
val expectedError = ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS val expectedError = ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS
Given { Given {
@ -636,7 +605,6 @@ class ReservationControllerTest(
}.When { }.When {
post("/reservations/waiting/${waiting.id!!}/confirm") post("/reservations/waiting/${waiting.id!!}/confirm")
}.Then { }.Then {
log().all()
statusCode(expectedError.httpStatus.value()) statusCode(expectedError.httpStatus.value())
body("code", equalTo(expectedError.errorCode)) body("code", equalTo(expectedError.errorCode))
} }
@ -645,7 +613,7 @@ class ReservationControllerTest(
context("POST /reservations/waiting/{id}/reject") { context("POST /reservations/waiting/{id}/reject") {
test("관리자만 거절할 수 있다.") { test("관리자만 거절할 수 있다.") {
login(MemberFixture.create(role = Role.MEMBER)) login(testDataHelper.createMember(role = Role.MEMBER))
val expectedError = AuthErrorCode.ACCESS_DENIED val expectedError = AuthErrorCode.ACCESS_DENIED
Given { Given {
@ -659,9 +627,8 @@ class ReservationControllerTest(
} }
test("거절된 예약은 삭제된다.") { test("거절된 예약은 삭제된다.") {
val member = login(MemberFixture.create(role = Role.ADMIN)) login(testDataHelper.createMember(role = Role.ADMIN))
val reservation = createSingleReservation( val reservation = testDataHelper.createReservation(
member = member,
status = ReservationStatus.WAITING status = ReservationStatus.WAITING
) )
@ -673,125 +640,91 @@ class ReservationControllerTest(
statusCode(204) statusCode(204)
} }
transactionTemplate.executeWithoutResult { _ -> val rejected = transactionTemplate.execute {
entityManager.find( entityManager.find(ReservationEntity::class.java, reservation.id)
ReservationEntity::class.java,
reservation.id
) shouldBe null
} }
rejected shouldBe null
} }
} }
} }
}
fun createSingleReservation(
date: LocalDate = LocalDate.now().plusDays(1), class TestDataHelper(
time: LocalTime = LocalTime.now(), private val entityManager: EntityManager,
themeName: String = "Default Theme", private val transactionTemplate: TransactionTemplate,
member: MemberEntity = MemberFixture.create(role = Role.MEMBER), ) {
status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED private var memberSequence = 0L
): ReservationEntity { private var themeSequence = 0L
return ReservationFixture.create( private var timeSequence = 0L
date = date,
theme = ThemeFixture.create(name = themeName), fun createMember(
time = TimeFixture.create(startAt = time), role: Role = Role.MEMBER,
member = member, account: String = "member${++memberSequence}@test.com",
status = status ): MemberEntity {
).also { it -> val member = MemberFixture.create(role = role, account = account)
transactionTemplate.execute { _ -> return persist(member)
if (member.id == null) { }
entityManager.persist(member)
} fun createTheme(name: String = "theme-${++themeSequence}"): ThemeEntity {
entityManager.persist(it.time) val theme = ThemeFixture.create(name = name)
entityManager.persist(it.theme) return persist(theme)
entityManager.persist(it) }
entityManager.flush()
entityManager.clear() fun createTime(startAt: LocalTime = LocalTime.of(10, 0).plusMinutes(++timeSequence * 10)): TimeEntity {
} val time = TimeFixture.create(startAt = startAt)
} return persist(time)
} }
fun createDummyReservations(): MutableMap<MemberEntity, MutableList<ReservationEntity>> { fun createReservation(
val reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>> = mutableMapOf() date: LocalDate = LocalDate.now().plusDays(1),
val members: List<MemberEntity> = listOf( theme: ThemeEntity = createTheme(),
MemberFixture.create(role = Role.MEMBER), time: TimeEntity = createTime(),
MemberFixture.create(role = Role.MEMBER) member: MemberEntity = createMember(),
) status: ReservationStatus = ReservationStatus.CONFIRMED,
): ReservationEntity {
transactionTemplate.executeWithoutResult { val reservation = ReservationFixture.create(
members.forEach { member -> date = date,
entityManager.persist(member) theme = theme,
} time = time,
entityManager.flush() member = member,
entityManager.clear() status = status
} )
return persist(reservation)
transactionTemplate.executeWithoutResult { }
repeat(10) { index ->
val theme = ThemeFixture.create(name = "theme$index") fun createPayment(reservation: ReservationEntity): PaymentEntity {
val time = TimeFixture.create(startAt = LocalTime.now().plusMinutes(index.toLong())) val payment = PaymentFixture.create(reservation = reservation)
entityManager.persist(theme) return persist(payment)
entityManager.persist(time) }
val reservation = ReservationFixture.create( fun createReservationRequest(
date = LocalDate.now().plusDays(index.toLong()), theme: ThemeEntity = createTheme(),
theme = theme, time: TimeEntity = createTime(),
time = time, ): ReservationCreateWithPaymentRequest {
member = members[index % members.size], return ReservationFixture.createRequest(
status = ReservationStatus.CONFIRMED themeId = theme.id!!,
) timeId = time.id!!,
entityManager.persist(reservation) )
reservations.getOrPut(reservation.member) { mutableListOf() }.add(reservation) }
}
entityManager.flush() fun createDummyReservations(
entityManager.clear() memberCount: Int = 2,
} reservationCount: Int = 10,
): Map<MemberEntity, List<ReservationEntity>> {
return reservations val members = (1..memberCount).map { createMember(role = Role.MEMBER) }
} val reservations = (1..reservationCount).map { index ->
createReservation(
fun createRequest( member = members[index % memberCount],
theme: ThemeEntity = ThemeFixture.create(), status = ReservationStatus.CONFIRMED
time: TimeEntity = TimeFixture.create(), )
): ReservationCreateWithPaymentRequest { }
lateinit var reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest return reservations.groupBy { it.member }
}
transactionTemplate.executeWithoutResult {
entityManager.persist(theme) private fun <T> persist(entity: T): T {
entityManager.persist(time) transactionTemplate.executeWithoutResult {
entityManager.persist(entity)
reservationCreateWithPaymentRequest = ReservationFixture.createRequest( }
themeId = theme.id!!, return entity
timeId = time.id!!, }
)
entityManager.flush()
entityManager.clear()
}
return reservationCreateWithPaymentRequest
}
fun login(member: MemberEntity): MemberEntity {
if (member.id == null) {
transactionTemplate.executeWithoutResult {
entityManager.persist(member)
entityManager.flush()
entityManager.clear()
}
}
every {
jwtHandler.getMemberIdFromToken(any())
} returns member.id!!
every {
memberService.findById(member.id!!)
} returns member
every {
memberIdResolver.resolveArgument(any(), any(), any(), any())
} returns member.id!!
return member
}
} }

View File

@ -13,12 +13,13 @@ import roomescape.theme.infrastructure.persistence.ThemeEntity
import roomescape.theme.infrastructure.persistence.ThemeRepository import roomescape.theme.infrastructure.persistence.ThemeRepository
import roomescape.theme.web.ThemeCreateRequest import roomescape.theme.web.ThemeCreateRequest
import roomescape.theme.web.ThemeRetrieveResponse import roomescape.theme.web.ThemeRetrieveResponse
import roomescape.util.TsidFactory
import roomescape.util.ThemeFixture import roomescape.util.ThemeFixture
class ThemeServiceTest : FunSpec({ class ThemeServiceTest : FunSpec({
val themeRepository: ThemeRepository = mockk() val themeRepository: ThemeRepository = mockk()
val themeService = ThemeService(themeRepository) val themeService = ThemeService(TsidFactory, themeRepository)
context("findThemeById") { context("findThemeById") {
val themeId = 1L val themeId = 1L

View File

@ -8,7 +8,7 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import roomescape.theme.util.TestThemeCreateUtil import roomescape.theme.util.TestThemeCreateUtil
import java.time.LocalDate import java.time.LocalDate
@DataJpaTest @DataJpaTest(showSql = false)
class ThemeRepositoryTest( class ThemeRepositoryTest(
val themeRepository: ThemeRepository, val themeRepository: ThemeRepository,
val entityManager: EntityManager val entityManager: EntityManager

View File

@ -10,6 +10,7 @@ import io.mockk.just
import io.mockk.runs import io.mockk.runs
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import roomescape.auth.exception.AuthErrorCode import roomescape.auth.exception.AuthErrorCode

View File

@ -14,6 +14,7 @@ import roomescape.time.exception.TimeErrorCode
import roomescape.time.exception.TimeException import roomescape.time.exception.TimeException
import roomescape.time.infrastructure.persistence.TimeRepository import roomescape.time.infrastructure.persistence.TimeRepository
import roomescape.time.web.TimeCreateRequest import roomescape.time.web.TimeCreateRequest
import roomescape.util.TsidFactory
import roomescape.util.TimeFixture import roomescape.util.TimeFixture
import java.time.LocalTime import java.time.LocalTime
@ -22,6 +23,7 @@ class TimeServiceTest : FunSpec({
val reservationRepository: ReservationRepository = mockk() val reservationRepository: ReservationRepository = mockk()
val timeService = TimeService( val timeService = TimeService(
tsidFactory = TsidFactory,
timeRepository = timeRepository, timeRepository = timeRepository,
reservationRepository = reservationRepository reservationRepository = reservationRepository
) )

View File

@ -7,7 +7,7 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import roomescape.util.TimeFixture import roomescape.util.TimeFixture
import java.time.LocalTime import java.time.LocalTime
@DataJpaTest @DataJpaTest(showSql = false)
class TimeRepositoryTest( class TimeRepositoryTest(
val entityManager: EntityManager, val entityManager: EntityManager,
val timeRepository: TimeRepository, val timeRepository: TimeRepository,

View File

@ -9,6 +9,7 @@ import io.mockk.every
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.context.annotation.Import import org.springframework.context.annotation.Import
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext
import org.springframework.data.repository.findByIdOrNull import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
@ -27,7 +28,6 @@ import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
@WebMvcTest(TimeController::class) @WebMvcTest(TimeController::class)
@Import(JacksonConfig::class)
class TimeControllerTest( class TimeControllerTest(
val mockMvc: MockMvc, val mockMvc: MockMvc,
) : RoomescapeApiTest() { ) : RoomescapeApiTest() {

View File

@ -1,7 +1,9 @@
package roomescape.util package roomescape.util
import com.github.f4b6a3.tsid.TsidFactory
import roomescape.auth.infrastructure.jwt.JwtHandler import roomescape.auth.infrastructure.jwt.JwtHandler
import roomescape.auth.web.LoginRequest import roomescape.auth.web.LoginRequest
import roomescape.common.config.next
import roomescape.member.infrastructure.persistence.MemberEntity import roomescape.member.infrastructure.persistence.MemberEntity
import roomescape.member.infrastructure.persistence.Role import roomescape.member.infrastructure.persistence.Role
import roomescape.payment.infrastructure.client.PaymentApproveRequest import roomescape.payment.infrastructure.client.PaymentApproveRequest
@ -20,11 +22,14 @@ import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
import java.time.OffsetDateTime import java.time.OffsetDateTime
val TsidFactory: TsidFactory = TsidFactory(0)
object MemberFixture { object MemberFixture {
const val NOT_LOGGED_IN_USERID: Long = 0 const val NOT_LOGGED_IN_USERID: Long = 0
fun create( fun create(
id: Long? = null, id: Long? = TsidFactory.next(),
name: String = "sangdol", name: String = "sangdol",
account: String = "default", account: String = "default",
password: String = "password", password: String = "password",
@ -56,14 +61,14 @@ object MemberFixture {
object TimeFixture { object TimeFixture {
fun create( fun create(
id: Long? = null, id: Long? = TsidFactory.next(),
startAt: LocalTime = LocalTime.now().plusHours(1), startAt: LocalTime = LocalTime.now().plusHours(1),
): TimeEntity = TimeEntity(id, startAt) ): TimeEntity = TimeEntity(id, startAt)
} }
object ThemeFixture { object ThemeFixture {
fun create( fun create(
id: Long? = null, id: Long? = TsidFactory.next(),
name: String = "Default Theme", name: String = "Default Theme",
description: String = "Default Description", description: String = "Default Description",
thumbnail: String = "https://example.com/default-thumbnail.jpg" thumbnail: String = "https://example.com/default-thumbnail.jpg"
@ -72,7 +77,7 @@ object ThemeFixture {
object ReservationFixture { object ReservationFixture {
fun create( fun create(
id: Long? = null, id: Long? = TsidFactory.next(),
date: LocalDate = LocalDate.now().plusWeeks(1), date: LocalDate = LocalDate.now().plusWeeks(1),
theme: ThemeEntity = ThemeFixture.create(), theme: ThemeEntity = ThemeFixture.create(),
time: TimeEntity = TimeFixture.create(), time: TimeEntity = TimeFixture.create(),
@ -125,14 +130,14 @@ object PaymentFixture {
const val AMOUNT: Long = 10000L const val AMOUNT: Long = 10000L
fun create( fun create(
id: Long? = null, id: Long? = TsidFactory.next(),
orderId: String = ORDER_ID, orderId: String = ORDER_ID,
paymentKey: String = PAYMENT_KEY, paymentKey: String = PAYMENT_KEY,
totalAmount: Long = AMOUNT, totalAmount: Long = AMOUNT,
reservation: ReservationEntity = ReservationFixture.create(id = 1L), reservation: ReservationEntity = ReservationFixture.create(id = 1L),
approvedAt: OffsetDateTime = OffsetDateTime.now() approvedAt: OffsetDateTime = OffsetDateTime.now()
): PaymentEntity = PaymentEntity( ): PaymentEntity = PaymentEntity(
id = id, _id = id,
orderId = orderId, orderId = orderId,
paymentKey = paymentKey, paymentKey = paymentKey,
totalAmount = totalAmount, totalAmount = totalAmount,
@ -141,14 +146,14 @@ object PaymentFixture {
) )
fun createCanceled( fun createCanceled(
id: Long? = null, id: Long? = TsidFactory.next(),
paymentKey: String = PAYMENT_KEY, paymentKey: String = PAYMENT_KEY,
cancelReason: String = "Test Cancel", cancelReason: String = "Test Cancel",
cancelAmount: Long = AMOUNT, cancelAmount: Long = AMOUNT,
approvedAt: OffsetDateTime = OffsetDateTime.now(), approvedAt: OffsetDateTime = OffsetDateTime.now(),
canceledAt: OffsetDateTime = approvedAt.plusHours(1) canceledAt: OffsetDateTime = approvedAt.plusHours(1)
): CanceledPaymentEntity = CanceledPaymentEntity( ): CanceledPaymentEntity = CanceledPaymentEntity(
id = id, _id = id,
paymentKey = paymentKey, paymentKey = paymentKey,
cancelReason = cancelReason, cancelReason = cancelReason,
cancelAmount = cancelAmount, cancelAmount = cancelAmount,

View File

@ -1,10 +1,16 @@
package roomescape.util package roomescape.util
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.github.f4b6a3.tsid.TsidFactory
import com.ninjasquad.springmockk.MockkBean import com.ninjasquad.springmockk.MockkBean
import com.ninjasquad.springmockk.SpykBean import com.ninjasquad.springmockk.SpykBean
import io.kotest.core.spec.style.BehaviorSpec import io.kotest.core.spec.style.BehaviorSpec
import io.mockk.every import io.mockk.every
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Import
import org.springframework.context.annotation.Primary
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext
import org.springframework.data.repository.findByIdOrNull import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpHeaders import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType import org.springframework.http.MediaType
@ -21,6 +27,8 @@ import roomescape.member.infrastructure.persistence.MemberEntity
import roomescape.member.infrastructure.persistence.MemberRepository import roomescape.member.infrastructure.persistence.MemberRepository
import roomescape.util.MemberFixture.NOT_LOGGED_IN_USERID import roomescape.util.MemberFixture.NOT_LOGGED_IN_USERID
@Import(TestConfig::class, JacksonConfig::class)
@MockkBean(JpaMetamodelMappingContext::class)
abstract class RoomescapeApiTest : BehaviorSpec() { abstract class RoomescapeApiTest : BehaviorSpec() {
@SpykBean @SpykBean
@ -128,3 +136,10 @@ abstract class RoomescapeApiTest : BehaviorSpec() {
""".trimIndent() """.trimIndent()
) )
} }
@TestConfiguration
class TestConfig {
@Bean
@Primary
fun tsidFactory(): TsidFactory = TsidFactory
}