영어 데일리

mockk을 활용한 고급 모킹 기법 (25.01.09)

현욱 정리장 2025. 1. 9. 20:37

https://medium.com/proandroiddev/advanced-mocking-techniques-with-mockk-part-2-of-5-04e0d3d6a08f

 

Advanced Mocking Techniques with MockK Part 2 of 5

part1

proandroiddev.com

 

이제 Mockk의 기본에 대해 익숙해졌으니, 더 발전해서 고급 스킬을 설명하겠습니다. 다음과 같은 내용을 다루겠습니다

  • Mocking 정적 함수, 객체, 그리고 final class
  • mockkStatic, mockkObject 그리고 mockkClass 사용 방법
  • 메서드 인자를 확인하기 위한 slot 과, capture 같은 고급 구조 

이것이 바로 Mockk이 Mockito와 같은 라이브러리와 비교해 진가를 발휘하는 부분입니다. 

 

 

Mocking 정적 함수

때때로, mock 정적 함수가 필요할때가 있습니다.

인스턴스가 아니라 클래스에 직접 연결된 성가신 함수들이요. Mockk에서는 이를 위해 mockkStatic 을 사용할 수 있습니다.

 

mockkStatic 예제

유틸 클래스 내에 정적 메서드가 있다고 가정해봅시다.

object Utils {
    fun getCurrentTime(): Long = System.currentTimeMillis()
}

 

테스트에서는 실제 시스템 시간에 의존하지 않는것이 좋습니다. 대신 getCurrentTime()에 모킹하여 고정된 값을 반환하게 할 수 있습니다

import io.mockk.every
import io.mockk.mockkStatic
import org.junit.Assert.assertEquals
import org.junit.Test
class UtilsTest {
    @Test
    fun `test getCurrentTime returns mocked value`() {
        // Mock the static method
        mockkStatic(Utils::class)
        every { Utils.getCurrentTime() } returns 1234567890L
        // Call the method
        val result = Utils.getCurrentTime()
        // Verify the result
        assertEquals(1234567890L, result)
    }
}

 

핵심

  • mockkStatic() 호출 전에 정적 함수를 스터빙 해야 하는걸 기억하세요
  • 만약 너가 필요하면 unmockkStatic(Util::class)를 통해 mock을 리셋시킬 수 있습니다.

 

객체 모킹

때때로  코드에 singleton 객체를 모킹해야 하는 경우가 있습니다. mockk은 mockkobject를 통해 이를 쉽게 해줍니다.

 

mockkObject 예제

싱글턴 객체가 있다고 가정해봅시다. 

object ConfigManager {
    fun getApiUrl(): String = "https://real-api.com"
}

 

 

이 객체를 테스트 중에 모킹하려면 

import io.mockk.every
import io.mockk.mockkObject
import org.junit.Assert.assertEquals
import org.junit.Test

class ConfigManagerTest {
    @Test
    fun `test getApiUrl returns mocked value`() {
        // Mock the singleton object
        mockkObject(ConfigManager)
        every { ConfigManager.getApiUrl() } returns "https://mock-api.com"
        // Call the method
        val result = ConfigManager.getApiUrl()
        // Verify the result
        assertEquals("https://mock-api.com", result)
    }
}

 

핵심

  • 싱글턴 객체를 모킹하려면 mockkObject를 사용하세요.
  • 만약 너가 모킹 리셋이 필요하다면 unmockkObject(ConfigManager)를 사용하세요.

 

final 클래스 모킹하기

코틀린에서 모든 클래스는 기본적으로 fianl로 선언되어있어, 이를 상속할수는 없습니다. 일부 모킹라이브러리는 이것땜에 어려움을 겪지만 Mockk은 mockkClass를 이용해 쉽게 핸들링할 수 있습니다.

 

mockkClass 예제

다음과 같은 final class가 있다고 가정해보겠습니다.

class UserRepository {
    fun getUser(id: Int): String = "Real User"
}

이 final class를 테스트에서 모킹하려면 

import io.mockk.every
import io.mockk.mockkClass
import org.junit.Assert.assertEquals
import org.junit.Test
class UserRepositoryTest {
    @Test
    fun `test getUser returns mocked value`() {
        // Create a mock of the final class
        val mockUserRepository = mockkClass(UserRepository::class)
        every { mockUserRepository.getUser(1) } returns "Mocked User"
        // Call the method
        val result = mockUserRepository.getUser(1)
        // Verify the result
        assertEquals("Mocked User", result)
    }
}

 

핵심

  • mockkclass는 final class들에게서 동작합니다. mockk은 코틀린을 완벽하게 만들어줍니다.
  • 만약 필요하면 unmockkAll()을 이용해 mock을 초기화할수있습니다.

 

slot과 capture의 대한 고급 개념 

때로는 모킹된 함수에 전달된 인수를 캡쳐하여 나중에 이를 검사해야 할때가 있습니다. mockk은 slot과 capture를 통해 이 목적을 달성할 수 있게 제공합니다.

 

slot이 뭔가요? 

slot은 모킹된 함수에 값을 전달할 때에 어딘가에 저장할 수 있게 해주는 역할입니다.

 

slot사용예제

API를 통해 유저 아이디를 가져올 수 있다고 가정해봅시다.

class ApiService {
    fun sendUserId(userId: Int) {
        // Sends the user ID to an API
    }
}

 

이 유저아이디를 보내서 정합한 유저인지 검증하고 싶다면, capture 를 사용해 인자값을 전달하면됩니다. 

import io.mockk.CapturingSlot
import io.mockk.every
import io.mockk.just
import io.mockk.runs
import io.mockk.slot
import io.mockk.verify
import org.junit.Assert.assertEquals
import org.junit.Test
class ApiServiceTest {
    @Test
    fun `test sendUserId captures the correct user ID`() {
        val apiService = mockk<ApiService>()
        val userIdSlot = slot<Int>()
        // Stub the method to capture the argument
        every { apiService.sendUserId(capture(userIdSlot)) } just runs
        // Call the method
        apiService.sendUserId(42)
        // Verify the captured value
        assertEquals(42, userIdSlot.captured)
    }
}

 

핵심

  • capture(userIdSlot) 은 함수를 통해 값을 전달 후 저장할 수 있습니다.
  • userIdSlot.captured를 이용해 captured 값을 검사할 수 있습니다.

 

mockk 으로 안드로이드 구성요소 단위 테스트

mockk은 viewModel. LiveData, Activity, fragment와 같은 안드로이드 구성요소 테스트를 할 수 있게 도와줍니다.  여기 빠른 예제가 있습니다.

 

ViewModel 모킹

유저 데이터를 가져오는 UserViewModel이 있다고 가정해보겠습니다. 

class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
    val userName = MutableLiveData<String>()
    fun loadUser(userId: Int) {
        val name = userRepository.getUser(userId)
        userName.value = name
    }
}

 

Mockk을 이용한 뷰모델 테스트

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test

class UserViewModelTest {
    @get:Rule
    val rule = InstantTaskExecutorRule()
    @Test
    fun `test loadUser updates userName LiveData`() {
        val mockUserRepository = mockk<UserRepository>()
        val viewModel = UserViewModel(mockUserRepository)
        val observer = mockk<Observer<String>>(relaxed = true)
        // Stub the repository
        every { mockUserRepository.getUser(1) } returns "John Doe"
        // Observe the LiveData
        viewModel.userName.observeForever(observer)
        // Call the function
        viewModel.loadUser(1)
        // Verify the LiveData was updated
        verify { observer.onChanged("John Doe") }
    }
}

핵심

  • InstantTaskExecutorRule은 liveData 테스트를 위해 사용됩니다.
  • mockk<Observer<T>>()는 모킹된 라이브러리를 관찰하기 위해 사용됩니다.

 

결론

축하합니다! 너는 다음과 같은 부분을 배웠습니다.

  • 정적 함수를 모킹하기 위한 mockkStatic
  • 싱글턴 객체를 모킹하기 위한 mockkObject
  • final class를 모킹하기 위한 mockkClass
  • slot 과 capture 고급 구조 사용

 

다음 기사에서는 coroutine과 flow를 포함한 mockk을 이용해 비동기 코드를 테스트하는 방법을 배워보겠습니다 

 

기타

comfortable 익숙하다 (편안하다)

really shines 진가 

Le'ts say ~가정해봅시다 

rely on 의존하다 

struggle 어려움을 겪다 

constructs 구조