Post

Kotlin Exposed UnitTest

나는 Kotlin 에서 RDBMS 를 사용 할 때, 주로 Exposed 를 사용한다.

이유는 제대로 된 ORM 이 없던 시절 부터, 프로그래밍을 해 온 습관이 남은건지, 아직도 SQL 과 모양이 비슷한 것을 선호하고, T지금까지 해 온 업무 특성상 조건부 쿼리를 쓸 일이 많았다보니 조건 조합이 간편 한 것을 좋아한다. 오타로 고생한 경험 덕분에 TypeSafety 를 좋아 하는 것은 덤이다. 그런 의미에서 Spring Data JPA 는 나에게 비주얼상 최악이고, RAW JPA 는 TypeSafety 하지 않다. Jooq 은 유료다. 그래서 QuertDSL 이 일반적으로 많이 쓰이는 것으로 알고 있는데 개발이 3년가까이 중단되었다가 올해 초 간신히 의존성 업데이트 정도만 올라왔을 정도로 관리가 제대로 안되고 있는 상태다.

하지만 최근 이슈가 생겼는데, 결과적으론 코드의 테스트 커버리지를 올려야 하게 되었다. 여기에서 Exposed 는 단점이 있는데… Entity 가 POJO 가 아니라서 UnitTest 가 단순하지 않다. 이부분 해결 해 주는 라이브러리가 나왔으면 좋겠다.

그래서 크게 4개를 Mock 해야 하는데 그 목록은 다음과 같다. 아래의 목록은 내 코드 구조상 Mock 이 필요 한 것들이고, 구조에 따라 필요하지 않을 수도 있다.

라이브러리 조합은 Kotlin + Exposed + Kotest + Mockk 이다.

1. transaction 함수

  • 사실 exposed-spring-boot-starter 의존성을 추가하고 @Transactional 을 사용하면 transaction 함수를 사용 하지 않아도 되지만, 난 이미 transaction 으로 만들어 놓은 코드가 많고, @Transactional 는 Scope 가 눈에 잘 안보여서 별로 선호하지 않는다. 아래 코드로 Mock 을 시도한다.
    방식은 간단한데, transaction() 을 mock 한 후, slot 으로 lambda 를 받아서 호출 해 주는 방식이다. 이것을 Mock 하지 않으면 DB 연결을 요구하여 exception 이 발생한다.
    클래스명인 org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt 는 그냥 바이트코드 디컴파일 해서 알아냈다. Exposed 버전이 바뀌면 같이 바뀌게 될거니 조심하자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private val TRANSACTION_CLASS_NAME = "org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManagerKt"

fun mockTransaction() {
    mockkStatic(TRANSACTION_CLASS_NAME)

    val transactionSlot = slot<Transaction.() -> Any>()
    every { transaction(any(), any(), any(), capture(transactionSlot)) } answers {
        transactionSlot.captured.invoke(mockk())
    }
}

fun unMockTransaction() {
    unmockkStatic(TRANSACTION_CLASS_NAME)
}

2. Primary Key

  • Exposed 는 Key 가 단순한 Value 가 아니라 한번 EntityID 로 Wrapping 되어 있다. 일반적으로 생성이 힘든 객체이므로 Mock 하는것이 좋다
1
2
val mockUserId = mockk<EntityID<Long>>()
every { mockUserId.value } returns 1L

3. Entity

  • Exposed 의 Entity 는 생성시 DB 연결을 요구하며, 생성자로 생성이 불가능하다 Mock 해야 한다
1
val mockUser = mockk<User>()

4. Entity 의 Property

  • Entity 의 Property 들은, Table 에 Delegate 되는 Property 목록이라 DB 연결을 요구한다. Mock 해야 한다
    제일 귀찮지만 사용하지 않는 Property 는 안해도 된 다는 것에 위안을 삼자
1
2
3
4
every { mockUser.id } returns mockUserId
every { mockUser.userName } returns "Lyn"
every { mockUser.age } returns 18
every { mockUser.age = any() } just runs

이렇게 하면 JPA 대비 무지 불편하지만, 일단 UnitTest 자체는 가능하다. 테스트 하면서 사용한 코드는 Github 에 올려놓았다.

PS. 제일 좋아하는 ORM 은 Entity Framework다.

This post is licensed under CC BY 4.0 by the author.