본문 바로가기
Spring

[Spring] Kotest에서 @Transactional을 사용하는 방법

by 노력남자 2023. 12. 10.
반응형

이번 포스팅에선 Kotest로 작성된 테스트 코드에서 @Transactional을 사용하는 방법에 대해 알아보겠다.

 

@Transactional을 선언해보자

 

아래 테스트 코드는 Kotest의 DescribeSpec으로 작성된 코드다.

 

@Transactional을 붙여보겠다.

 

@Transactional
@SpringBootTest
class UserRepositoryTest(
    private val userRepository: UserRepository
) : DescribeSpec({

    describe("save") {
        context("User를 저장하면") {
            userRepository.save(User(name = "노력남자"))

            it("정상적으로 저장된다.") {
                userRepository.findAll().size shouldBe 1
            }
        }
    }

    describe("saveAll") {
        context("User 2개를 저장하면") {
            userRepository.saveAll(
                listOf(
                    User(name = "노력남자1"),
                    User(name = "노력남자2")
                )
            )

            it("2개가 정상적으로 저장된다.") {
                userRepository.findAll().size shouldBe 2
            }
        }
    }
})

 

역시 그냥 될리가 없다.

 

save 테스트에 저장된 데이터가 롤백이 되지 않아 saveAll 테스트에서 에러가 발생했다.

 

 

SpringExtension을 설정하자

 

Kotest 공식 사이트에서 방법을 찾았다.

 

Kotest Extensions에 SpringExtension을 추가해줘야 한다.

 

1. io.kotest.extensions:kotest-extensions-spring 라이브러리가 필요하다. 의존성 추가를 해주자.

 

testImplementation("io.kotest.extensions:kotest-extensions-spring:1.1.3")

 

2. 그 다음 Kotest extension에 SpringExtension을 추가해주자.

 

- AbstractProjectConfig를 상속받는 방법

 

class KotestProjectConfig : AbstractProjectConfig() {
    override fun extensions() = listOf(SpringExtension)
}

 

- 각 Test에 설정하는 방법 

 

@Transactional
@SpringBootTest
class UserRepositoryTest(
    private val userRepository: UserRepository
) : DescribeSpec({
    ...
}) {
    override fun extensions() = listOf(SpringExtension)
}

 

자 이제 설정이 다 됐다. 

 

다시 테스트를 돌려보자.

 

오우.. 똑같이 에러가 발생한다.

 

 

어라.. 설정이 되지 않은 걸까?

 

로그를 보면 transaction begin, rollback이 잘 나오는 걸 봐서는 뭔가 설정이 된 거 같긴하다.

 

 

찾아보니 SpringExtension에 SpringTestLifecycleMode 때문에 실패했었던 거다.

 

SpringTestLifecycleMode

 

SpringTestLifecycleMode에 영향을 받는 건 Nested 구조를 갖고 있는 테스트다.

 

나는 DescribeSpec을 사용하고 있기 때문에 영향을 받았다.

 

SpringTestLifecycleMode는 Root, Test 두 개다.

 

 

SpringTestLifecycleMode.Root

 

SpringTestLifecycleMode.Root는 Root 단위로 트랜잭션을 묶는 mode다.

 

설정하는 방법은 아래와 같다.

 

class KotestProjectConfig : AbstractProjectConfig() {
    override fun extensions() = listOf(SpringTestExtension(SpringTestLifecycleMode.Root))
}

 

Root란 Spec 바로 아래에 있는 Test를 말한다. (DescribeSpec에선 describe, BehaviorSpec에선 given)

 

 

Kotest 버전마다 Root를 찾아내는 로직이 조금씩 다른데 Spec 바로 아래 있는 Test를 찾고자 하는 건 동일하다. 

(SpringTestExtension.kt에 isApplicable()을 보면된다.)

 

트랜잭션은 describe가 Root로 설정되어 describe 단위로 트랜잭션이 발생한다.

 

 

테스트를 돌려보면 성공으로 나온다.

 

이전에 돌린 테스트는 왜 실패했을까?

 

위에서 SpringExtension 설정한 방식은 SpringTestLifecycleMode가 Test이기 때문이다.

 

class KotestProjectConfig : AbstractProjectConfig() {
    override fun extensions() = listOf(SpringExtension)
}

 

 

SpringTestLifecycleMode.Test

 

SpringTestLifecycleMode.Root는 Test 단위로 트랜잭션을 묶는 mode다.

 

설정 방법은 아래와 같다.

 

class KotestProjectConfig : AbstractProjectConfig() {
    override fun extensions() = listOf(SpringTestExtension(SpringTestLifecycleMode.Test))
}

 

Test는 TestType이 Test나 Dynamic인 걸 말한다. (DescribeSpec에선 it, BehaviorSpec에선 then)

 

 

트랜잭션은 it 단위로 돈다. 그렇기 때문에 데이터 저장도 it 안에서 해야한다.

 

아까는 context에서 했기 때문에 제대로 롤백이 안 된 거다.

 

 

위 테스트들을 보면 context나 when 단위로 트랜잭션을 묶으려면 어떻게 해야 하는 건지 의문이 든다.

 

그건 다음 포스팅에서 다루겠다.

반응형

댓글