kotlin - 安卓 : unit testing of LiveData and Flow

标签 kotlin android-livedata android-viewmodel kotlin-coroutines

我正在尝试为我的 ViewModel 编写单元测试,但我不知道如何处理 LiveData 函数。

具体来说,我无法验证接收 LiveData Observer 的所有值。

关于我有一个发出值然后传递为 LiveData 的流用例,测试 操作 功能的最佳方法是什么?

在下面的代码中,您可以发现我只能读取值“endLoading”,但我想检查所有值:“startLoading” , “Hello Dummy $input”, “endLoading”

MainViewModel.kt

class MainViewModel(val useCase: DummyUseCase = DummyUseCase()): ViewModel() {
    fun operation(value: Int): LiveData<String> = useCase.invoke(value)
        .transform { response ->
            emit(response)
        }.onStart {
            emit("startLoading")
        }.catch {
            emit("ERROR")
        }.onCompletion {
            emit("endLoading")
        }.asLiveData(viewModelScope.coroutineContext)
}

MainViewModelTest.kt

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@ExperimentalCoroutinesApi
class MainViewModelTest {
    //region Setup
    @get:Rule
    val rule = InstantTaskExecutorRule()
    private val testDispatcher = TestCoroutineDispatcher()

    @MockK private lateinit var stateObserver: Observer<String>
    @MockK private lateinit var useCase: DummyUseCase
    private lateinit var viewModel: MainViewModel

    @Before
    fun setup() {
        MockKAnnotations.init(this, relaxUnitFun = true)
        Dispatchers.setMain(testDispatcher)
        viewModel = MainViewModel(useCase)
    }

    @After
    fun teardown() {
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }
    //endregion

    @Test // AAA testing
    fun `when my flow succeeds, return a state String`() {
        runBlocking {
            //Arrange
            val input = 10
            coEvery { useCase.invoke(input) }.returns(flow {
                emit("Hello Dummy $input")
            })

            //Act
            val actual = viewModel.operation(input).apply {
                observeForever(stateObserver)
            }

            //Assert
            // I want to assert here every value received in the observer of the "actual" LiveData
            // How? :(
            assertNotNull(actual.value) // is always "endLoading"
        }
    }
}

最佳答案

您可以使用自定义 Observer<T> 测试 LiveData执行。创建一个观察者,记录所有发出的值并让您针对历史进行断言。

记录值的观察者可能如下所示:

class TestableObserver<T> : Observer<T> {
    private val history: MutableList<T> = mutableListOf()

    override fun onChanged(value: T) {
        history.add(value)
    }

    fun assertAllEmitted(values: List<T>) {
        assertEquals(values.count(), history.count())

        history.forEachIndexed { index, t ->
            assertEquals(values[index], t)
        }
    }
}

您可以使用 assertAllEmitted(...) 断言 LiveData 是否发出所有给定值。功能。

测试函数将使用 TestableObserver 的实例类而不是模拟类:

@Test // AAA testing
fun `when my flow succeeds, return a state String`() {
    runBlocking {
        //Arrange
        val stateObserver = TestableObserver<String>()

        val input = 10
        coEvery { useCase.invoke(input) }.returns(flow {
            emit("Hello Dummy $input")
        })

        //Act
        val actual = viewModel.operation(input).apply {
            observeForever(stateObserver)
        }

        //Assert
        stateObserver.assertAllEmitted(
            listOf(
                "startLoading",
                "Hello Dummy 10",
                "endLoading"
            )
        )
    }
}

使用模拟框架和断言框架可以断言 LiveData 的历史,但是,我认为实现这个可测试的观察者更具可读性。

关于kotlin - 安卓 : unit testing of LiveData and Flow,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67169360/

相关文章:

kotlin - 在 Kotlin 中使用条件选择排序属性

android - Kotlin Flow 与 LiveData

android - 在不使用 Room 的情况下使用 LiveData 和 ViewModel

android - ViewModel 在导航导航中没有被清除,并且 viewmodel 中的实时数据保持 Activity 状态

java - Java 的 String[] 的 Kotlin 等价物是什么?

android - Android Studio 中的 Kotlin 自动完成覆盖

spring-boot - kotlintest如何测试spring boot应用

Android LiveData - 第二次更新时不会触发 switchMap

android - 类型不匹配。必需:观察员<in int!>找到:?

android - 使用 viewLifecycleOwner 作为 LifecycleOwner