Android

[Android] VewModel LiveData Unit Test

Xmobile 2020. 10. 12. 11:07

ViewModel 에서 api통신후 LiveData를 참조하여 정상적으로 갱신되었는지 확인하여 

정상 동작하는지 테스트를 진행해 보도록 하겠습니다. 

 

LiveData확인을 위한 확장함수를 활용하여 진행 하도록 합니다. 

https://github.com/android/architecture-components-samples/blob/master/LiveDataSample/app/src/test/java/com/android/example/livedatabuilder/util/LiveDataTestUtil.kt

 

android/architecture-components-samples

Samples for Android Architecture Components. . Contribute to android/architecture-components-samples development by creating an account on GitHub.

github.com

 

build.gradle

// Testing dependencies
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.5"
kaptAndroidTest "com.google.dagger:hilt-android-compiler:2.28.3-alpha"
androidTestImplementation "androidx.arch.core:core-testing:2.0.0"
// Espresso dependencies
androidTestImplementation "androidx.test.espresso:espresso-contrib:3.1.1"
androidTestImplementation "androidx.test.espresso:espresso-core:3.1.1"
androidTestImplementation "androidx.test.espresso:espresso-intents:3.1.1"
// Assertions
androidTestImplementation "androidx.test.ext:junit:1.1.0"
androidTestImplementation "com.google.truth:truth:0.42"
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0"
androidTestImplementation "androidx.work:work-testing:2.1.0"
androidTestImplementation "com.google.dagger:hilt-android-testing:2.28.3-alpha"
testImplementation "junit:junit:4.12"

 

MainRepository

class MainRepository(private val apiService: ApiService) {
    suspend fun getLotto(order: Int) = apiService.getLotto(order)
}

 

ApiService

interface ApiService {
    @GET("api/lotto")
    suspend fun getLotto(@Query("order") order: Int ): HomeDto

    companion object {
        private const val BASE_URL = "http://192.168.0.103:8181/"

        fun create(): ApiService {
            val logger = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }

            val client = OkHttpClient.Builder()
                .addInterceptor(logger)
                .build()

            return Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
                .create(ApiService::class.java)
        }
    }
}

 

LiveDataTestUtil

@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)

    try {
        afterObserve.invoke()
        // Don't wait indefinitely if the LiveData is not set.
        if (!latch.await(time, timeUnit)) {
            throw TimeoutException("LiveData value was never set.")
        }
    } finally {
        this.removeObserver(observer)
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}

 

HomeViewModel

getLotto메소드는 retrofit을 사용하여 서버 api를 호출 후 homeDto 라이브 데이터를 갱신합니다.

class HomeViewModel @ViewModelInject constructor(val mainRepository: MainRepository
) : ViewModel() {
    var isLoading = MutableLiveData<Boolean>()
    var homeDto = MutableLiveData<HomeDto>()

    fun getLotto(order: Int) {
        try {
            isLoading.postValue(true)
            viewModelScope.launch {
                homeDto.postValue(mainRepository.getLotto(order))
            }
        } catch (e: Exception) {
            homeDto.postValue(null)
        } finally {
            isLoading.postValue(false)
        }
    }
}

 

HomeViewModelTest

Api 호출후 livedata를 확인하도록 합니다. 

@HiltAndroidTest
@ExperimentalCoroutinesApi
class HomeViewModelTest {
    private lateinit var viewModel: HomeViewModel
    private val hiltRule = HiltAndroidRule(this)
    private val instantTaskExecutorRule = InstantTaskExecutorRule()

    @get:Rule
    val rule = RuleChain
        .outerRule(hiltRule)
        .around(instantTaskExecutorRule)

    @Inject
    lateinit var mainRepository: MainRepository

    @Before
    fun setUp() {
        hiltRule.inject()
        val context = InstrumentationRegistry.getInstrumentation().targetContext
        viewModel = HomeViewModel(mainRepository)
    }

    @Test
    fun callRequest()  {
        //호출 후 livedata를 확인 
        viewModel.getLotto(0)
        assertTrue(viewModel.homeDto.getOrAwaitValue()._order > 0 )
    }
}

정상적으로 테스트 확인. 

이를 바탕으로 viewmodel livedata을 검증하는 방법에 대해서 확인해 보았습니다.