美文网首页
使用 Mockk 和 Truth 在 Android 上进行单元

使用 Mockk 和 Truth 在 Android 上进行单元

作者: 安安_660c | 来源:发表于2022-08-12 09:39 被阅读0次

    介绍

    之前的文章解释了如何开始编写单元测试以及Mockk的一些更高级的特性。一旦你了解了这一点,就有必要继续测试稍微复杂的东西,尤其是当我们要测试ViewModel类型的类时。本文收集了实现它所需的所有“技巧”。

    Testeando corrutinas

    当我们使用 ViewModel 时,对于许多功能来说,它必须是这样的,这是很正常的:

    fun doSomething() = viewModelScope.launch {
    ...
    }
    
    

    注意:如果你想了解更多关于协程、作用域等的知识,可以从这里开始。

    首先,尝试suspend在正常测试中调用函数会产生编译时错误,因为测试也应该是一个suspend function。为此,我们有一个新关键字runTest. 让我们看看它是如何使用的:

    @Test
    fun testSomething() = runTest {
    // your test that uses coroutines here
    }
    
    

    有了这个,我们正在创建一个执行测试的范围,所以我们已经可以suspend functions在其中使用它了。但是,如果我们尝试测试使用viewModelScope.launch { ... }它的函数,我们可能会发现一个错误,告诉我们有关调度程序的一些信息。为此,我们必须更改Dispatcher,这样做是这样的:

    @Before
    fun setUp() {
        Dispatchers.setMain(UnconfinedTestDispatcher())
        // all your other initialization code here
    }
    
    @After
    fun tearDown() {
        Dispatchers.resetMain()
        // all your other cleanup code here
    }
    
    @Test
    fun testSomething() = runTest {
        // call your test here
    }
    
    

    有了这个,我们可以为我们的 ViewModel 中使用viewModelScope.launch { ... }.

    注意:如果您想了解更多关于 Dispatchers、替代品UnconfinedTestDispatcher等的信息,可以访问这篇文章

    测试 LiveData 值

    虽然看起来想法是逐渐用Flows代替它们,这是 Kotlin 的典型并且不使用 Android 框架;您的应用可能仍在使用LiveDataViewModel 来更改 UI 中的内容。如果是这样,您在请求 LiveData 值以检查其结果时可能会遇到竞争条件。

    幸运的是,没有什么是无法修复的,为此我们将编写以下扩展函数:

    import androidx.annotation.VisibleForTesting
    import androidx.lifecycle.LiveData
    import androidx.lifecycle.Observer
    import java.util.concurrent.CountDownLatch
    import java.util.concurrent.TimeUnit
    import java.util.concurrent.TimeoutException
    
    @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
    }
    
    

    基本上这个函数的作用是我们可以向 LiveData 请求一个值,最多等待 2 秒让它改变。如果值没有改变,则该方法抛出异常,如果改变,则返回该值。

    我们可以这样调用该方法:

    @Test
    fun testSomething() = runTest {
        // call your method that changes some live data value
        val result = yourLiveData.getOrAwaitValue()
        assertThat(result).isEqualTo(5)
    }
    
    

    但是,如果我们按原样这样做,我们会发现代码中引发了异常。这是因为我们需要添加一个规则,它只是测试类开头的几行代码。

    让我们看一个例子。如果我们有一个显示汽车细节的视图,UI 中的灯在引擎运行时打开,绑定到 aLiveData<Boolean>并且我们想要测试它的行为,我们将执行以下操作:

    class CarDetailViewModelTest {
    
        @get:Rule
        val rule = InstantTaskExecutorRule()
    
        private lateinit var viewModel: CarDetailViewModel
    
        @Before
        fun setUp() {
            viewModel = CarDetailViewModel()
        }
    
        @After
        fun tearDown() {
            unmockkAll()
        }
    
        @Test
        fun `Turn on engine sets the isOn LiveData to true`() = runTest {
            viewModel.turnOnEngine()
            val liveDataValue = viewModel.isOn.getOrAwaitValue()
            assertThat(liveDataValue).isTrue()
        }
    }
    
    

    我们感兴趣的行是这些:

    @get:Rule
    val rule = InstantTaskExecutorRule()
    
    

    有了这个,我们已经可以对 LiveData 类型的变量进行测试了。

    Nota para MediatorLiveData

    如果您使用MediatorLiveData组合不同的值MutableLiveData,您将看到您的测试失败。这是因为MediatorLiveData如果没有人在看,a 永远不会更新,所以我们需要首先observer在测试中创建 a:

    @Test
    fun testSomething() = runTest {
        // Arrange
        viewModel.myMediatorLiveData.observeForever {}
        // Update the mediator live data sources
    
        // Act
    
        // Assert
    }
    
    

    完成此操作后,我们可以管理MediatorLiveData.

    测试流程

    为了完成这篇文章,我们将看到另一个可以执行测试的典型组件:流。例如,一个典型的用例是使用SharedFlow向 UI 发送事件,或使用StateFlow而不是LiveData.

    注意:如果你想了解更多关于SharedFlowand的内容StateFlow,可以看下面的文章

    为了测试 Flows,我们需要一个名为Turbine的新库。我们可以像添加任何其他库一样在build.gradle应用程序模块中添加它:

    testImplementation 'app.cash.turbine:turbine:0.9.0'
    
    

    这个库所做的是向 Flows 添加一个扩展函数,称为test,以及更多期望值的函数等。

    让我们通过一个示例来看看如何检查在 a 中发出的值SharedFlow(使用我们在 LiveData 部分中看到的相同示例):

    @Test
    fun `Turn on engine emits true in SharedFlow`() = runTest {
        viewModel.isOn.test {
            viewModel.turnOnEngine()
            val item = awaitItem()
            assertThat(item).isTrue()
        }
    }
    
    

    需要注意的重要一点是,如果一个项目从未发出过,则测试不会失败,而是会挂起。为此,我们可以将参数传递给runTest,称为dispatchTimeoutMs,如果测试未在该时间内完成,这将导致测试失败。唯一的变化是这样的:

    @Test
    fun `Turn on engine emits true in SharedFlow`() = runTest(dispatchTimeoutMs = 50) {
        viewModel.isOn.test {
            viewModel.turnOnEngine()
            val item = awaitItem()
            assertThat(item).isTrue()
        }
    }
    

    链接:https://cmhernandezdel.hashnode.dev/tests-unitarios-en-android-con-mockk-y-truth-iii-tests-que-implican-corrutinas-livedata-y-flow

    相关文章

      网友评论

          本文标题:使用 Mockk 和 Truth 在 Android 上进行单元

          本文链接:https://www.haomeiwen.com/subject/clexgrtx.html