首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >测试LiveData转换?

测试LiveData转换?
EN

Stack Overflow用户
提问于 2018-08-12 15:39:22
回答 2查看 7.3K关注 0票数 19

我使用Architecture组件和反应性方法构建了一个Splash屏幕。我从Preferences LiveData object fun isFirstLaunchLD(): SharedPreferencesLiveData<Boolean>返回。我有ViewModel将LiveData传递给视图并更新首选项

代码语言:javascript
复制
val isFirstLaunch = Transformations.map(preferences.isFirstLaunchLD()) { isFirstLaunch ->
    if (isFirstLaunch) {
        preferences.isFirstLaunch = false
    }
    isFirstLaunch
}

在我的片段中,我从ViewModel中观察到了ViewModel

代码语言:javascript
复制
    viewModel.isFirstLaunch.observe(this, Observer { isFirstLaunch ->
        if (isFirstLaunch) {
            animationView.playAnimation()
        } else {
            navigateNext()
        }
    })

我想现在就测试我的ViewModel,看看isFirstLaunch是否被正确更新。我怎么测试它呢?我把所有的层都正确地分开了吗?您会在这个示例代码上编写什么样的测试?

EN

回答 2

Stack Overflow用户

发布于 2019-07-31 00:34:52

我把所有的层都正确地分开了吗?

这两层似乎合理地分开了。逻辑在ViewModel中,而您是不指在ViewModel中存储Android视图/片段/活动

您会在这个示例代码上编写什么样的测试?

在测试您的ViewModel时,您可以在此代码上编写工具测试或纯单元测试。对于单元测试,您可能需要弄清楚如何对首选项进行双重测试,这样您就可以专注于isFirstLaunch/map行为。一个简单的方法是通过一个假的偏好测试双到ViewModel。

我怎么测试它呢?

我写了一个关于测试LiveData转换的简介,继续读!

测试LiveData变换

Tl;DR您可以测试LiveData转换,您只需要确保遵守转换的结果 LiveData。

事实1:如果没有观察到,LiveData就不会发出数据。LiveData的"生命周期意识“完全是为了避免额外的工作。LiveData知道它的观察者(通常是活动/片段)所处的生命周期状态。这使得LiveData能够知道屏幕上的任何东西是否正在观察到它。如果没有观察到LiveData,或者它们的观察者在屏幕外,就不会触发观察者(不调用观察者的onChanged方法)。这是有用的,因为它阻止您做额外的工作,例如“更新/显示”屏幕外片段。

要触发转换,必须观察转换生成的事实2:必须遵守转换生成的LiveData。要触发转换,必须观察结果LiveData (在本例中为isFirstLaunch)。同样,在没有观察的情况下,LiveData观察者不会被触发,转换也不会触发。

在单元测试ViewModel时,您不应该或不需要访问片段/活动。如果你不能以正常的方式设置一个观察者,你如何进行单元测试?

事实3:在您的测试中,您不需要LifecycleOwner来观察LiveData,您可以使用observeForever --您不需要使用生命周期观察者来测试LiveData。这是令人困惑的,因为通常在测试之外(即在您的生产代码中),您将像使用一个活动或片段一样使用LifecycleObserver

在测试中,您可以使用LiveData方法observeForever()来观察而不需要生命周期所有者。这个观察者是“总是”观察的,并且没有打开/关闭屏幕的概念,因为没有LifecycleOwner。因此,您必须使用removeObserver(观察者)手动删除观察者。

将所有这些放在一起,您可以使用observeForever测试转换代码:

代码语言:javascript
复制
class ViewModelTest {

    // Executes each task synchronously using Architecture Components.
    // For tests and required for LiveData to function deterministically!
    @get:Rule
    val rule = InstantTaskExecutorRule()


    @Test
    fun isFirstLaunchTest() {

        // Create observer - no need for it to do anything!
        val observer = Observer<Boolean> {}

        try {
            // Sets up the state you're testing for in the VM
            // This affects the INPUT LiveData of the transformation
            viewModel.someMethodThatAffectsFirstLaunchLiveData()

            // Observe the OUTPUT LiveData forever
            // Even though the observer itself doesn't do anything
            // it ensures any map functions needed to calculate
            // isFirstLaunch will be run.
            viewModel.isFirstLaunch.observeForever(observer)

            assertEquals(viewModel.isFirstLaunch.value, true)
        } finally {
            // Whatever happens, don't forget to remove the observer!
            viewModel.isFirstLaunch.removeObserver(observer)
        }
    }

}

几个注意事项:

  • 您需要使用InstantTaskExecutorRule()来使您的LiveData更新同步执行。您将需要androidx.arch.core:core-testing:<current-version>来使用此规则。
  • 虽然您经常会在测试代码中看到observeForever,但有时它也会进入生产代码。请记住,当您在生产代码中使用observeForever时,您将失去生命周期意识的好处。你还必须确保不要忘记移除观察者!

最后,如果您正在编写大量这些测试,尝试、观察-捕获-删除-代码可能会变得乏味。如果您使用的是Kotlin,您可以创建一个扩展函数来简化代码并避免忘记删除观察者的可能性。有两种选择:

选项1

代码语言:javascript
复制
/**
 * Observes a [LiveData] until the `block` is done executing.
 */
fun <T> LiveData<T>.observeForTesting(block: () -> Unit) {
    val observer = Observer<T> { }
    try {
        observeForever(observer)
        block()
    } finally {
        removeObserver(observer)
    }
}

这将使测试看起来像:

代码语言:javascript
复制
class ViewModelTest {

    @get:Rule
    val rule = InstantTaskExecutorRule()


    @Test
    fun isFirstLaunchTest() {

        viewModel.someMethodThatAffectsFirstLaunchLiveData()

        // observeForTesting using the OUTPUT livedata
        viewModel.isFirstLaunch.observeForTesting {

            assertEquals(viewModel.isFirstLaunch.value, true)

        }
    }

}

选项2

代码语言:javascript
复制
@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
}

这将使测试看起来像:

代码语言:javascript
复制
class ViewModelTest {

    @get:Rule
    val rule = InstantTaskExecutorRule()

    @Test
    fun isFirstLaunchTest() {

        viewModel.someMethodThatAffectsFirstLaunchLiveData()

        // getOrAwaitValue using the OUTPUT livedata        
        assertEquals(viewModel.isFirstLaunch.getOrAwaitValue(), true)

    }
}

这些选项都是从体系结构蓝图的反应性分支中提取的。

票数 24
EN

Stack Overflow用户

发布于 2019-01-17 13:57:37

这取决于您的SharedPreferencesLiveData所做的工作。

如果SharedPreferencesLiveData包含特定于Android的类,您将无法正确地测试它,因为JUnit无法访问Android特定的类。

另一个问题是,要能够观察LiveData,您需要某种生命周期所有者。(原邮政编码中的这个。)

在单元测试中,可以简单地将“this”替换为以下内容:

代码语言:javascript
复制
private fun lifecycle(): Lifecycle {
    val lifecycle = LifecycleRegistry(Mockito.mock(LifecycleOwner::class.java))
    lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
    return lifecycle
}

然后以下列方式使用:

代码语言:javascript
复制
@RunWith(MockitoJUnitRunner::class)
class ViewModelTest {

    @Rule
    @JvmField
    val liveDataImmediateRule = InstantTaskExecutorRule()

    @Test
    fun viewModelShouldLoadAttributeForConsent() {
        var isLaunchedEvent: Boolean = False

        // Pseudo code - Create ViewModel

        viewModel.isFirstLaunch.observe(lifecycle(), Observer { isLaunchedEvent = it } )

        assertEquals(true, isLaunchedEvent)
    }

    private fun lifecycle(): Lifecycle {
        val lifecycle = LifecycleRegistry(Mockito.mock(LifecycleOwner::class.java))
        lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
        return lifecycle
    }
}

注意:您必须有规则在场,这样LiveData就可以立即执行,而不是随时执行。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51810330

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档