diff --git a/common/persistence/build.gradle.kts b/common/persistence/build.gradle.kts index 2c715a02..bf2cb2ad 100644 --- a/common/persistence/build.gradle.kts +++ b/common/persistence/build.gradle.kts @@ -1,4 +1,3 @@ -import org.gradle.kotlin.dsl.named import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { @@ -15,6 +14,7 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("io.kotest:kotest-runner-junit5:5.9.1") testImplementation("io.kotest.extensions:kotest-extensions-spring:1.3.0") + testImplementation("io.mockk:mockk:1.14.4") implementation(project(":common:utils")) implementation(project(":common:types")) diff --git a/common/persistence/src/test/kotlin/com/sangdol/common/persistence/TransactionExecutionUtilTest.kt b/common/persistence/src/test/kotlin/com/sangdol/common/persistence/TransactionExecutionUtilTest.kt new file mode 100644 index 00000000..64f4faa6 --- /dev/null +++ b/common/persistence/src/test/kotlin/com/sangdol/common/persistence/TransactionExecutionUtilTest.kt @@ -0,0 +1,75 @@ +package com.sangdol.common.persistence + +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.equality.shouldBeEqualUsingFields +import io.kotest.matchers.nulls.shouldNotBeNull +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 org.junit.jupiter.api.assertThrows +import org.springframework.transaction.PlatformTransactionManager +import org.springframework.transaction.TransactionDefinition +import org.springframework.transaction.TransactionStatus +import org.springframework.transaction.support.DefaultTransactionDefinition + +class TransactionExecutionUtilTest() : FunSpec() { + private val transactionManager = mockk(relaxed = true) + private val transactionExecutionUtil = TransactionExecutionUtil(transactionManager) + + init { + context("withNewTransaction") { + + beforeTest { + clearMocks(transactionManager) + } + + val body = TestPersistableBaseEntity(123458192L, "hello") + + test("지정한 action이 성공하면, 해당 값을 반환하고 트랜잭션을 커밋한다.") { + transactionExecutionUtil.withNewTransaction(isReadOnly = false) { + body + }.also { + it.shouldNotBeNull() + it shouldBeEqualUsingFields body + verify { transactionManager.commit(any()) } + verify(exactly = 0) { transactionManager.rollback(any()) } + } + } + + test("지정한 action 실행 도중 예외가 발생하면, 예외를 던지고 트랜잭션을 롤백한다.") { + assertThrows { + transactionExecutionUtil.withNewTransaction(isReadOnly = false) { + throw RuntimeException() + } + }.also { + verify { transactionManager.rollback(any()) } + verify(exactly = 0) { transactionManager.commit(any()) } + } + } + + test("isReadOnly=true 지정시 읽기 전용 트랜잭션으로 실행한다.") { + val transactionStatus = mockk(relaxed = true) + val transactionDefinitionSlot = slot() + + every { + transactionManager.getTransaction(capture(transactionDefinitionSlot)) + } returns transactionStatus + + transactionExecutionUtil.withNewTransaction(isReadOnly = true) { + "hello" + }.also { + assertSoftly(transactionDefinitionSlot.captured) { + this.isReadOnly shouldBe true + this.propagationBehavior shouldBe DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW + } + + verify { transactionManager.commit(any()) } + } + } + } + } +}