이번 포스팅에선 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 단위로 트랜잭션을 묶으려면 어떻게 해야 하는 건지 의문이 든다.
그건 다음 포스팅에서 다루겠다.
댓글