ABOUT ME

나의 공부 기록

Today
Yesterday
Total
  • 👾 MockK와 Mockito
    🔖 Kotlin 2025. 1. 9. 17:16

    📌 Mockito

    @Test
    fun `mockito 테스트 예제`() {
        // given
        val username = "beomsic"
        val user = User(username)
    
        given(userRepository.save(any())).willReturn(user)
    
        // when
        userService.save(user)
    
        // then
        then(userRepository).should().save(any())
    }

    Mockito를 사용하면 BDDMockito를 통해서 BDD 형태로 작성할 수 있다.

    • when은 given으로 verify는 then으로 사용

     

    📌 MockK

    코틀린에서 테스트 시 Mock 객체를 생성하는 것을 도와주는 라이브러리

    @Test
    fun `mockk 테스트 예제`() {
        // given
        val username = "beomsic"
        val user = User(username)
    
        every { userRepository.save(any()) } returns (user)
    
        // when
        userService.save(user)
    
        // then
        verify { userRepository.save(any()) }
    }

     

    ⚠️ Mockito의 문제점

    1️⃣ 확장함수

    코틀린에서는 확장 함수 기능을 제공을 해주고 있다.

    예외 처리 로직을 확장 함수로 정의하여 중복 코드를 제거하고, 더 간결한 코드로 예외를 처리할 수 있도록 한 예시코드

    interface TestRepository : JpaRepository<TestEntity, Long> {
    }
    
    fun TestRepository.findEntityById(id: Long): TestEntity {
        return findByIdOrNull(id) ?: throw NotFoundException()
    }
    
    ----
    val entity = testRepository.findEntityById(id)
    • kotlin의 확장 함수를 사용해 새로운 기능을 추가

     

    하지만 확장 함수를 사용하면 Mocktio를 사용해 테스트를 할 수 없다.

     

    [Mockito-kotlin Issue] https://github.com/mockito/mockito-kotlin/issues/198

    • Mockito으로 Kotlin Extension Function을 Mocking하지 말아라

     

    확장 함수는 정적(static) 메서드이다.

    • kotiln의 확장 함수는 실제로 수신 객체의 메서드가 아닌 정적 메서드로 컴파일

     

    Mockito 라이브러리는 정적 메서드를 mocking하는 것을 지원하지 않고 있다.

    이와 달리, MockK는 확장함수도 mocking 처리가 가능해 의도대로 테스트를 할 수 있다.

     

    class TestRepositoryExtensionTest {
    
        private val testRepository: TestRepository = mockk()
    
        @Test
        fun `findEntityById`() {
            // given
            val testEntity = TestEntity(id = 1L, name = "Test Entity")
            
            every { testRepository.findEntityById(1L) } returns testEntity
    
            // when
            val result = testService.findById(1L)
    
            // then
            verify { userRepository.findEntityById(1L) }
            assertThat(result.name).isEqualTo(testEntity.name)
        }
    }

     

    2️⃣ object

    Kotlin의 object는 프로그램 실행 중 한 번만 초기화되고, 항상 동일한 인스턴스를 사용합니다.

    object BCryptUtils {
        fun hash(password: String) =
            BCrypt.withDefaults().hashToString(12, password.toCharArray())
    
        fun verify(password: String, hashedPassword: String) =
            BCrypt.verifyer().verify(password.toCharArray(), hashedPassword).verified
    }

     

    이렇게 프로젝트 개발시 유틸리티 class나 mapper 형태에서 주로 사용한다.

    Mockito는 기본적으로 클래스의 인스턴스를 대상으로 동작

    • 클래스의 인스턴스 메서드인터페이스 메서드를 가로채(mock)도록 설계

     

    ❗object는 정적 필드로 관리되는 싱글톤, object의 메서드는 정적 메서드처럼 작동하기 때문에 정적 멤버나 메서드를 기본적으로 지원하지 않는 Mockito는 mocking을 할 수 없다.

     

    MockK는 kotlin의 특성을 고려해 설계되었기 때문에 object class의 Mocking을 지원한다.

    class BCryptUtilsTest {
    
        @Test
        fun `test BCryptUtils hash method`() {
            mockkObject(BCryptUtils)
    
            every { BCryptUtils.hash("password123") } returns "mockedHashedPassword"
    
            val hashedPassword = BCryptUtils.hash("password123")
            assert(hashedPassword == "mockedHashedPassword")
            unmockkObject(BCryptUtils)
        }
    }
    • mockkObject() 함수를 이용해 대상 object를 넣어 기본 mocking 방법처럼 적용
    • unmockkObject() 를 이용해 테스트 후 mocking 해제를 할 수 있다.

     

    📖 참고자료

    MockK

    코틀린 테스트에서 mockk를 써야하는 이유

    코틀린에서의 내부 가시성과 MockK를 활용한 테스트 전략

    Mock singleton objects and static methods | MockK Guidebook

    '🔖 Kotlin' 카테고리의 다른 글

    😎 Infix Function  (0) 2024.12.30
    널 안정성  (0) 2024.12.29
    🦥 lateinit 과 lazy  (0) 2024.12.29
    Kotlin Coroutines  (0) 2024.12.28
    Unit / Nothing  (0) 2024.11.25

    댓글

Designed by Tistory.