영어 데일리

mockk을 이용한 안드로이드 구성요소 단위 테스트 (25.01.10)

현욱 정리장 2025. 1. 10. 09:12

https://medium.com/proandroiddev/unit-testing-android-components-with-mockk-part-3-of-5-584a99352921

 

Unit Testing Android Components with MockK Part 3 of 5

https://medium.com/proandroiddev/introduction-to-mockk-and-mocking-basics-part-1-of-5-01467d917e2f

proandroiddev.com

 

이 기사에서는 viewmodel, LiveData, Activity,Frgment와 같은 안드로이드 구성요소를 mockk 으로 테스트하는 방법에 대해서 설명하겠습니다. Junit과 코루틴 같은 라이브러리  mockk 통합 설명과, flow와 suspend 함수 같은 비동기 코드 테스트 방법에 대해 설명하겠습니다. 

 

ViewModel과 LiveData 모킹 

왜 viewModel을 테스트해야하나요?

viewmodel들은 UI 관련된 데이터를 관리하는 안드로이드 아키텍쳐에서 중요한 부분을 차지합니다. 뷰모델을 테스트 한다는 것은 정확한 business 로직을 보장하고, livedata가 예상대로 업데이트 되는 것을 보장할 수 있습니다.

 

Mockk을 활용한 ViewModel 모킹 예제

유저 데이터를 가져오고, 그리고 이를 liveData를 통해 노출하는 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과 Junit을 통해 뷰모델을 테스트 하는 방법입니다

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 작업이 동기적으로 실행되게 해줍니다.
  • Mock을 옵저버하려면 mockk<Observer<T>>(). 
  • LiveData 변경 검증은 verify { observer.onCHanged(value) }. 사용

 

mockk을 사용한 Activity와 Fragment 테스팅

왜 Activity들과 Fragment들을 테스트해야하나요?

 

Activity와 Fragments는  유저 경험과 너의 앱 UI에 핵심적인 부분입니다.  이들을 테스트하면 올바르게 동작 할 수 있는지 보장할 수 있습니다. 특히 ViewModels, LiveData와 그외 구성요소요. 

 

Activity 의존성 모킹 

UserViewModel을 통해 유저 상세정보를 가져오고 보여주는 액티비티가 있다고 가정해보겠습니다.

class UserActivity : AppCompatActivity() {
    private val viewModel: UserViewModel by lazy { ViewModelProvider(this).get(UserViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)
        viewModel.userName.observe(this) { name ->
            findViewById<TextView>(R.id.userNameTextView).text = name
        }
    }
}

 

Activity 테스트 

mockk와 Junit을 이용해 액티비티를 테스트 하는 방법

import androidx.lifecycle.MutableLiveData
import androidx.test.core.app.ActivityScenario
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Test
class UserActivityTest {
    @Test
    fun `test UserActivity displays user name correctly`() {
        // Mock the ViewModel
        val mockViewModel = mockk<UserViewModel>(relaxed = true)
        val fakeLiveData = MutableLiveData<String>()
        every { mockViewModel.userName } returns fakeLiveData
        // Launch the Activity
        val scenario = ActivityScenario.launch(UserActivity::class.java)
        scenario.onActivity { activity ->
            // Inject the mocked ViewModel
            activity.viewModel = mockViewModel
            fakeLiveData.value = "Jane Doe"
            val textView = activity.findViewById<TextView>(R.id.userNameTextView)
            assertEquals("Jane Doe", textView.text)
        }
    }
}

 

  • ActivityScenario 를 사용하여 액티비티를 실행하고 상호작용하세요. 
  • 모킹을 액티비티에 주입함으로써 실제 종속성으로부터 분리하세요
  • 정확한 UI 업데이트 검증

모킹 통합 코루틴과 Junit

왜 코루틴을 테스트해야하나요? 

코루틴은 비동기 테스트를 쉽게하지만, 테스트하기가 복잡합니다. Mockk은 suspend 함수를 핸들링하기 위해 coEvery와 coVerify를 제공합니다. 

 

코루틴 함수 테스트 예제

suspend function을 사용한 UserRepository가 있다고 가정해보겠습니다. 

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

 

레포지토리를 사용하는 ViewModel을  테스트 하는 방법 

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

 

Mockk과 코루틴 테스트

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
class UserViewModelCoroutineTest {
    @get:Rule
    val rule = InstantTaskExecutorRule()
    @Test
    fun `test loadUser updates userName LiveData with coroutine`() = runTest {
        val mockRepository = mockk<UserRepository>()
        val viewModel = UserViewModel(mockRepository)
        coEvery { mockRepository.getUser(1) } returns "John Doe"
        viewModel.loadUser(1)
        coVerify { mockRepository.getUser(1) }
        assertEquals("John Doe", viewModel.userName.value)
    }
}

 

  • suspend 함수는 coEvery 와 coVerify 를 사용합니다.
  • runTest 사용은 코루틴 코드를 테스트 합니다.
  • 테스트 중에는 Dispatchers.Main은 Dispatchers.Unconfined로 대체할 수 있습니다.

Mockk 을 이용한 flow테스트 

Flows는 데이터를 스트림 하는 좋은 방법입니다. Flow 테스트는 복잡합니다. 하지만 mockk과 coroutines를 제공해 쉽게 툴을 사용할 수 있습니다. 

 

Flow 테스트 방법 예제 

유저 이름을 가져오는 flow 레포지토리가 있다고 가정해보겠습니다. 

class UserRepository {
    fun getUserFlow(): Flow<String> = flow {
        emit("John Doe")
    }
}

 

flow 테스트

import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Test
class UserRepositoryFlowTest {
    @Test
    fun `test getUserFlow emits correct value`() = runTest {
        val mockRepository = mockk<UserRepository>()
        coEvery { mockRepository.getUserFlow() } returns flowOf("Jane Doe")
        val result = mockRepository.getUserFlow().toList()
        assertEquals(listOf("Jane Doe"), result)
    }
}

 

핵심 

  • flowOf를 사용하면 Flow mock을 쉽게 생성할 수 있습니다.
  • toList() 사용은 flow 출을 검증할 수 있습니다.

결론

너는 이부분을 배웠습니다

  • Mockk을 이용한 ViewModels, LiveData, Activity, Fragment 모킹 
  • junit과 코루틴 mockk 통합
  • coEvery, coVerify와 Flow를 사용한  동기 테스트

이 스킬들을 이용해 너는 안정적인 단위 테스트를 너의 안드로이드 앱에 잘 작성할 수 있습니다. 

 

기타

integrating 통합

a crucial part 중요한 부분 

ensure 보장하다 

central 핵심적인 

behave 동작하다 

via a ~을 통해 

isolate 분리하다

suppose 가정하다  

tricky 까다로운 

will-equipped 잘 준비된