test: payment에서의 Finder, Writer 도입으로 인한 테스트 수정

This commit is contained in:
이상진 2025-08-05 23:00:58 +09:00
parent b865dc01f6
commit 393dceb355
5 changed files with 338 additions and 105 deletions

View File

@ -4,114 +4,162 @@ import io.kotest.assertions.assertSoftly
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.shouldBeInstanceOf
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.slot
import roomescape.payment.exception.PaymentErrorCode
import roomescape.payment.exception.PaymentException
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
import roomescape.payment.infrastructure.persistence.PaymentRepository
import roomescape.payment.implement.PaymentFinder
import roomescape.payment.implement.PaymentWriter
import roomescape.payment.infrastructure.client.PaymentApproveResponse
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
import roomescape.payment.web.PaymentCancelRequest
import roomescape.payment.web.PaymentCancelResponse
import roomescape.util.PaymentFixture
import roomescape.util.TsidFactory
import roomescape.util.ReservationFixture
import java.time.LocalDate
import java.time.LocalTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
class PaymentServiceTest : FunSpec({
val paymentRepository: PaymentRepository = mockk()
val canceledPaymentRepository: CanceledPaymentRepository = mockk()
val paymentFinder: PaymentFinder = mockk()
val paymentWriter: PaymentWriter = mockk()
val paymentService = PaymentService(TsidFactory, paymentRepository, canceledPaymentRepository)
val paymentService = PaymentService(paymentFinder, paymentWriter)
context("createCanceledPaymentByReservationId") {
val reservationId = 1L
test("reservationId로 paymentKey를 찾을 수 없으면 예외를 던진다.") {
every { paymentRepository.findPaymentKeyByReservationId(reservationId) } returns null
context("createPayment") {
val approvedPaymentInfo = PaymentApproveResponse(
paymentKey = "paymentKey",
orderId = "orderId",
totalAmount = 1000L,
approvedAt = OffsetDateTime.now(),
)
val reservation = ReservationFixture.create(id = 1L)
val exception = shouldThrow<PaymentException> {
paymentService.createCanceledPaymentByReservationId(reservationId)
test("정상 응답") {
every {
paymentWriter.create(
paymentKey = approvedPaymentInfo.paymentKey,
orderId = approvedPaymentInfo.orderId,
totalAmount = approvedPaymentInfo.totalAmount,
approvedAt = approvedPaymentInfo.approvedAt,
reservation = reservation
)
} returns PaymentFixture.create(
id = 1L,
orderId = approvedPaymentInfo.orderId,
paymentKey = approvedPaymentInfo.paymentKey,
totalAmount = approvedPaymentInfo.totalAmount,
approvedAt = approvedPaymentInfo.approvedAt,
reservation = reservation
)
val response = paymentService.createPayment(approvedPaymentInfo, reservation)
assertSoftly(response) {
it.id shouldBe 1L
it.paymentKey shouldBe approvedPaymentInfo.paymentKey
it.reservationId shouldBe reservation.id
}
exception.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
}
}
context("reservationId로 paymentKey를 찾고난 후") {
val paymentKey = "test-payment-key"
context("createCanceledPayment(canceledPaymentInfo)") {
val canceledPaymentInfo = PaymentCancelResponse(
cancelStatus = "normal",
cancelReason = "고객 요청",
cancelAmount = 1000L,
canceledAt = OffsetDateTime.now(),
)
val approvedAt = OffsetDateTime.now()
val paymentKey = "paymentKey"
test("CanceledPaymentEntity를 응답") {
every {
paymentWriter.createCanceled(
cancelReason = canceledPaymentInfo.cancelReason,
cancelAmount = canceledPaymentInfo.cancelAmount,
canceledAt = canceledPaymentInfo.canceledAt,
approvedAt = approvedAt,
paymentKey = paymentKey
)
} returns PaymentFixture.createCanceled(
id = 1L,
paymentKey = paymentKey,
cancelAmount = canceledPaymentInfo.cancelAmount
)
val response = paymentService.createCanceledPayment(canceledPaymentInfo, approvedAt, paymentKey)
response.shouldBeInstanceOf<CanceledPaymentEntity>()
response.paymentKey shouldBe paymentKey
}
}
context("createCanceledPayment(reservationId)") {
val reservationId = 1L
test("취소 사유를 '예약 취소'로 하여 PaymentCancelRequest를 응답") {
val payment = PaymentFixture.create(id = 1L, paymentKey = "paymentKey", totalAmount = 1000L)
every {
paymentFinder.findByReservationId(reservationId)
} returns payment
val cancelReasonSlot = slot<String>()
every {
paymentRepository.findPaymentKeyByReservationId(reservationId)
} returns paymentKey
paymentWriter.createCanceled(payment, capture(cancelReasonSlot), any())
} returns PaymentFixture.createCanceled(
id = 1L,
paymentKey = payment.paymentKey,
cancelAmount = payment.totalAmount
)
test("해당 paymentKey로 paymentEntity를 찾을 수 없으면 예외를 던진다.") {
every {
paymentRepository.findByPaymentKey(paymentKey)
} returns null
val response = paymentService.createCanceledPayment(reservationId)
val exception = shouldThrow<PaymentException> {
paymentService.createCanceledPaymentByReservationId(reservationId)
}
exception.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
}
response.shouldBeInstanceOf<PaymentCancelRequest>()
cancelReasonSlot.captured shouldBe "예약 취소"
}
test("해당 paymentKey로 paymentEntity를 찾고, cancelPaymentEntity를 저장한다.") {
val paymentEntity = PaymentFixture.create(paymentKey = paymentKey)
test("결제 정보가 없으면 예외 응답") {
every {
paymentFinder.findByReservationId(reservationId)
} throws PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
every {
paymentRepository.findByPaymentKey(paymentKey)
} returns paymentEntity.also {
every {
paymentRepository.delete(it)
} just runs
}
every {
canceledPaymentRepository.save(any())
} returns PaymentFixture.createCanceled(
id = 1L,
paymentKey = paymentKey,
cancelReason = "Test",
cancelAmount = paymentEntity.totalAmount,
)
val result: PaymentCancelRequest = paymentService.createCanceledPaymentByReservationId(reservationId)
assertSoftly(result) {
this.paymentKey shouldBe paymentKey
this.amount shouldBe paymentEntity.totalAmount
this.cancelReason shouldBe "Test"
}
shouldThrow<PaymentException> {
paymentService.createCanceledPayment(reservationId)
}.also {
it.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
}
}
}
context("updateCanceledTime") {
val paymentKey = "test-payment-key"
val canceledAt = OffsetDateTime.now()
val paymentKey = "paymentKey"
val canceledAt = OffsetDateTime.of(LocalDate.of(2025, 8, 5), LocalTime.of(10, 0), ZoneOffset.UTC)
test("paymentKey로 canceledPaymentEntity를 찾을 수 없으면 예외를 던진다.") {
test("정상 응답") {
val canceled = PaymentFixture.createCanceled(id = 1L)
every {
canceledPaymentRepository.findByPaymentKey(paymentKey)
} returns null
val exception = shouldThrow<PaymentException> {
paymentService.updateCanceledTime(paymentKey, canceledAt)
}
exception.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
}
test("paymentKey로 canceledPaymentEntity를 찾고, canceledAt을 업데이트한다.") {
val canceledPaymentEntity = PaymentFixture.createCanceled(
paymentKey = paymentKey,
canceledAt = canceledAt.minusMinutes(1)
)
every {
canceledPaymentRepository.findByPaymentKey(paymentKey)
} returns canceledPaymentEntity
paymentFinder.findCanceledByKey(paymentKey)
} returns canceled
paymentService.updateCanceledTime(paymentKey, canceledAt)
assertSoftly(canceledPaymentEntity) {
this.canceledAt shouldBe canceledAt
canceled.canceledAt shouldBe canceledAt
}
test("결제 취소 정보가 없으면 예외 응답") {
every {
paymentFinder.findCanceledByKey(paymentKey)
} throws PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
shouldThrow<PaymentException> {
paymentService.updateCanceledTime(paymentKey, canceledAt)
}.also {
it.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
}
}
}

View File

@ -0,0 +1,93 @@
package roomescape.payment.implement
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 io.mockk.verify
import roomescape.payment.exception.PaymentErrorCode
import roomescape.payment.exception.PaymentException
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
import roomescape.payment.infrastructure.persistence.PaymentRepository
class PaymentFinderTest : FunSpec({
val paymentRepository: PaymentRepository = mockk()
val canceledPaymentRepository: CanceledPaymentRepository = mockk()
val paymentFinder = PaymentFinder(paymentRepository, canceledPaymentRepository)
context("existsPaymentByReservationId") {
val reservationId = 1L
test("결제 정보가 있으면 true를 반환한다.") {
every {
paymentRepository.existsByReservationId(reservationId)
} returns true
paymentFinder.existsPaymentByReservationId(reservationId) shouldBe true
}
test("결제 정보가 없으면 false를 반환한다.") {
every {
paymentRepository.existsByReservationId(reservationId)
} returns false
paymentFinder.existsPaymentByReservationId(reservationId) shouldBe false
}
}
context("findByReservationId") {
val reservationId = 1L
test("결제 정보를 조회한다.") {
every {
paymentRepository.findByReservationId(reservationId)
} returns mockk()
paymentFinder.findByReservationId(reservationId)
verify(exactly = 1) {
paymentRepository.findByReservationId(reservationId)
}
}
test("결제 정보가 없으면 실패한다.") {
every {
paymentRepository.findByReservationId(reservationId)
} returns null
shouldThrow<PaymentException> {
paymentFinder.findByReservationId(reservationId)
}.also {
it.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
}
}
}
context("findCanceledByKey") {
val paymentKey = "paymentKey"
test("결제 취소 정보를 조회한다.") {
every {
canceledPaymentRepository.findByPaymentKey(paymentKey)
} returns mockk()
paymentFinder.findCanceledByKey(paymentKey)
verify(exactly = 1) {
canceledPaymentRepository.findByPaymentKey(paymentKey)
}
}
test("결제 취소 정보가 없으면 실패한다.") {
every {
canceledPaymentRepository.findByPaymentKey(paymentKey)
} returns null
shouldThrow<PaymentException> {
paymentFinder.findCanceledByKey(paymentKey)
}.also {
it.errorCode shouldBe PaymentErrorCode.CANCELED_PAYMENT_NOT_FOUND
}
}
}
})

View File

@ -0,0 +1,114 @@
package roomescape.payment.implement
import com.ninjasquad.springmockk.MockkClear
import com.ninjasquad.springmockk.clear
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.date.after
import io.kotest.matchers.shouldBe
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
import roomescape.payment.infrastructure.persistence.PaymentEntity
import roomescape.payment.infrastructure.persistence.PaymentRepository
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.util.PaymentFixture
import roomescape.util.ReservationFixture
import roomescape.util.TsidFactory
import java.time.OffsetDateTime
class PaymentWriterTest : FunSpec({
val paymentRepository: PaymentRepository = mockk()
val canceledPaymentRepository: CanceledPaymentRepository = mockk()
val paymentWriter = PaymentWriter(paymentRepository, canceledPaymentRepository, TsidFactory)
val paymentKey = "paymentKey"
val orderId = "orderId"
val totalAmount = 1000L
val approvedAt = OffsetDateTime.now()
context("create") {
val reservation: ReservationEntity = ReservationFixture.create(id = 1L)
test("결제 정보를 저장한다.") {
val slot = slot<PaymentEntity>()
every {
paymentRepository.save(capture(slot))
} returns mockk()
paymentWriter.create(paymentKey, orderId, totalAmount, approvedAt, reservation)
verify(exactly = 1) {
paymentRepository.save(any())
}
slot.captured.also {
it.paymentKey shouldBe paymentKey
it.orderId shouldBe orderId
it.totalAmount shouldBe totalAmount
it.approvedAt shouldBe approvedAt
}
paymentRepository.clear(MockkClear.AFTER)
}
}
context("createCanceled") {
val cancelReason = "고객 요청"
val canceledAt = OffsetDateTime.now()
afterTest {
clearMocks(canceledPaymentRepository)
}
test("PaymentEntity를 받아 저장한다.") {
val payment: PaymentEntity = PaymentFixture.create(id = 1L)
val slot = slot<CanceledPaymentEntity>()
every {
canceledPaymentRepository.save(capture(slot))
} returns mockk()
paymentWriter.createCanceled(payment, cancelReason, canceledAt)
verify(exactly = 1) {
canceledPaymentRepository.save(any())
}
slot.captured.also {
it.paymentKey shouldBe payment.paymentKey
it.cancelAmount shouldBe payment.totalAmount
it.approvedAt shouldBe payment.approvedAt
}
}
test("취소 정보를 받아 저장한다.") {
val slot = slot<CanceledPaymentEntity>()
every {
canceledPaymentRepository.save(capture(slot))
} returns mockk()
paymentWriter.createCanceled(
cancelReason = cancelReason,
cancelAmount = totalAmount,
canceledAt = canceledAt,
approvedAt = approvedAt,
paymentKey = paymentKey
)
verify(exactly = 1) {
canceledPaymentRepository.save(any())
}
slot.captured.also {
it.paymentKey shouldBe paymentKey
it.cancelAmount shouldBe totalAmount
it.approvedAt shouldBe approvedAt
}
}
}
})

View File

@ -39,28 +39,6 @@ class PaymentRepositoryTest(
}
}
context("findPaymentKeyByReservationId") {
lateinit var paymentKey: String
beforeTest {
reservation = setupReservation()
paymentKey = PaymentFixture.create(reservation = reservation)
.also { paymentRepository.save(it) }
.paymentKey
}
test("정상 반환") {
paymentRepository.findPaymentKeyByReservationId(reservation.id!!)
?.let { it shouldBe paymentKey }
?: throw AssertionError("Unexpected null value")
}
test("null 반환") {
paymentRepository.findPaymentKeyByReservationId(reservation.id!! + 1)
.also { it shouldBe null }
}
}
context("findByPaymentKey") {
lateinit var payment: PaymentEntity

View File

@ -13,8 +13,8 @@ import roomescape.payment.web.PaymentCancelRequest
import roomescape.payment.web.toCreateResponse
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationStatus
import roomescape.reservation.web.ReservationCreateResponse
import roomescape.reservation.web.ReservationCreateWithPaymentRequest
import roomescape.reservation.web.ReservationRetrieveResponse
import roomescape.util.*
class ReservationWithPaymentServiceTest : FunSpec({
@ -55,9 +55,9 @@ class ReservationWithPaymentServiceTest : FunSpec({
paymentService.createPayment(paymentApproveResponse, reservationEntity)
} returns paymentEntity.toCreateResponse()
val result: ReservationRetrieveResponse = reservationWithPaymentService.createReservationAndPayment(
val result: ReservationCreateResponse = reservationWithPaymentService.createReservationAndPayment(
request = reservationCreateWithPaymentRequest,
paymentInfo = paymentApproveResponse,
approvedPaymentInfo = paymentApproveResponse,
memberId = memberId
)
@ -81,7 +81,7 @@ class ReservationWithPaymentServiceTest : FunSpec({
)
every {
paymentService.createCanceledPaymentByReservationId(reservationEntity.id!!)
paymentService.createCanceledPayment(reservationEntity.id!!)
} returns paymentCancelRequest
every {
@ -100,7 +100,7 @@ class ReservationWithPaymentServiceTest : FunSpec({
context("isNotPaidReservation") {
test("결제된 예약이면 true를 반환한다.") {
every {
paymentService.isReservationPaid(reservationEntity.id!!)
paymentService.existsByReservationId(reservationEntity.id!!)
} returns false
val result: Boolean = reservationWithPaymentService.isNotPaidReservation(reservationEntity.id!!)
@ -110,7 +110,7 @@ class ReservationWithPaymentServiceTest : FunSpec({
test("결제되지 않은 예약이면 false를 반환한다.") {
every {
paymentService.isReservationPaid(reservationEntity.id!!)
paymentService.existsByReservationId(reservationEntity.id!!)
} returns true
val result: Boolean = reservationWithPaymentService.isNotPaidReservation(reservationEntity.id!!)