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 구조
'영어 데일리' 카테고리의 다른 글
mockk으로 suspend 함수와 flows를 모킹하는 방법 (25.01.11) (0) | 2025.01.12 |
---|---|
mockk을 이용한 안드로이드 구성요소 단위 테스트 (25.01.10) (0) | 2025.01.10 |
Mockk와 Mocking 기본 소개 (24.01.08) (1) | 2025.01.08 |
컴포즈 단점에 대한 반박댓글 (25.01.07) (0) | 2025.01.07 |
compose를 사용안하는 10가지 이유 (25.01.06) (0) | 2025.01.06 |