测试 LiveData 转换? [英] Testing LiveData Transformations?

本文介绍了测试 LiveData 转换?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用 Android 架构组件和响应式方法构建了一个启动画面.我从 Preferences LiveData 对象 fun isFirstLaunchLD(): SharedPreferencesLiveData 返回.我有 ViewModel 将 LiveData 传递给视图并更新首选项

I've built a Splash Screen using Android Architecture Components and Reactive approach. I return from Preferences LiveData object fun isFirstLaunchLD(): SharedPreferencesLiveData<Boolean>. I have ViewModel that passes LiveData to the view and updates Preferences

val isFirstLaunch = Transformations.map(preferences.isFirstLaunchLD()) { isFirstLaunch ->
    if (isFirstLaunch) {
        preferences.isFirstLaunch = false
    }
    isFirstLaunch
}

在我的 Fragment 中,我从 ViewModel 观察 LiveData

In my Fragment, I observe LiveData from ViewModel

    viewModel.isFirstLaunch.observe(this, Observer { isFirstLaunch ->
        if (isFirstLaunch) {
            animationView.playAnimation()
        } else {
            navigateNext()
        }
    })

我现在想测试我的 ViewModel,看看 isFirstLaunch 是否正确更新.我该如何测试?我是否正确分离了所有图层?您会针对此示例代码编写什么样的测试?

I would like to test my ViewModel now to see if isFirstLaunch is updated properly. How can I test it? Have I separated all layers correctly? What kind of tests would you write on this sample code?

推荐答案

我是否正确分离了所有图层?

Have I separated all layers correctly?

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

The layers seem reasonably separated. The logic is in the ViewModel and you're not referring to storing Android Views/Fragments/Activities in the ViewModel.

您会针对此示例代码编写什么样的测试?

What kind of tests would you write on this sample code?

在测试您的 ViewModel 时,您可以针对此代码编写检测或纯单元测试.对于单元测试,您可能需要弄清楚如何为首选项制作双重测试,以便您可以专注于 isFirstLaunch/map 行为.一个简单的方法是将一个虚假的偏好测试传递给 ViewModel.

When testing your ViewModel you can write instrumentation or pure unit tests on this code. For unit testing, you might need to figure out how to make a test double for preferences, so that you can focus on the isFirstLaunch/map behavior. An easy way to do that is passing a fake preference test double into the ViewModel.

我该如何测试?

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

I wrote a little blurb on testing LiveData Transformations, read on!

Tl;DR 您可以测试 LiveData 转换,您只需要确保观察到转换的 结果 LiveData.

Tl;DR You can test LiveData transformation, you just need to make sure the result LiveData of the Transformation is observed.

事实 1:如果未观察到,LiveData 不会发出数据. LiveData 的生命周期意识" 就是为了避免额外的工作.LiveData 知道它的观察者(通常是活动/片段)所处的生命周期状态.这允许 LiveData 知道它是否正在被屏幕上的任何东西观察到.如果没有观察到 LiveData 或者它们的观察者在屏幕外,则不会触发观察者(不调用观察者的 onChanged 方法).这很有用,因为它可以防止您做额外的工作更新/显示"屏幕外的 Fragment,例如.

Fact 1: LiveData doesn't emit data if it's not observed. LiveData's "lifecycle awareness" is all about avoiding extra work. LiveData knows what lifecycle state it's observers (usually Activities/Fragments) are in. This allows LiveData to know if it's being observed by anything actually on-screen. If LiveData aren't observed or if their observers are off-screen, the observers are not triggered (an observer's onChanged method isn't called). This is useful because it keeps you from doing extra work "updating/displaying" an off-screen Fragment, for example.

事实 2:必须观察转换生成的 LiveData 才能触发转换. 要触发转换,必须观察结果 LiveData(在本例中为 isFirstLaunch).同样,如果没有观察,LiveData 观察者不会被触发,转换也不会被触发.

Fact 2: LiveData generated by Transformations must be observed for the transformation to trigger. For Transformation to be triggered, the result LiveData (in this case, isFirstLaunch) must be observed. Again, without observation, the LiveData observers aren't triggered, and neither are the transformations.

当您对 ViewModel 进行单元测试时,您不应该拥有或需要访问片段/活动.如果不能以正常方式设置观察者,如何进行单元测试?

When you're unit testing a ViewModel, you shouldn't have or need access to a Fragment/Activity. If you can't set up an observer the normal way, how do you unit test?

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

Fact 3: In your tests, you don't need a LifecycleOwner to observe LiveData, you can use observeForever You do not need a lifecycle observer to be able to test LiveData. This is confusing because generally outside of tests (ie in your production code), you'll use a LifecycleObserver like an Activity or Fragment.

在测试中,您可以使用 LiveData 方法 observeForever() 到没有生命周期所有者的观察者.由于没有 LifecycleOwner,这个观察者总是"观察并且没有屏幕开/关的概念.因此,您必须使用 removeObserver(observer) 手动删除观察者.

In tests you can use the LiveData method observeForever() to observer without a lifecycle owner. This observer is "always" observing and doesn't have a concept of on/off screen since there's no LifecycleOwner. You must therefore manually remove the observer using removeObserver(observer).

综上所述,您可以使用observeForever 来测试您的转换代码:

Putting this all together, you can use observeForever to test your Transformations code:

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: 才能使用此规则.
  • 虽然您经常会在测试代码中看到 observeForever,但它有时也会进入生产代码.请记住,当您在生产代码中使用 observeForever 时,您将失去生命周期感知的好处.您还必须确保不要忘记移除观察者!
  • You need to use InstantTaskExecutorRule() to get your LiveData updates to execute synchronously. You'll need the androidx.arch.core:core-testing:<current-version> to use this rule.
  • While you'll often see observeForever in test code, it also sometimes makes its way into production code. Just keep in mind that when you're using observeForever in production code, you lose the benefits of lifecycle awareness. You must also make sure not to forget to remove the observer!

最后,如果您要编写很多这样的测试,try、observe-catch-remove-code 可能会变得乏味.如果您正在使用 Kotlin,您可以创建一个扩展函数来简化代码并避免忘记删除观察者的可能性.有两种选择:

Finally, if you're writing a lot of these tests, the try, observe-catch-remove-code can get tedious. If you're using Kotlin, you can make an extension function that will simplify the code and avoid the possibility of forgetting to remove the observer. There are two options:

选项 1

/**
 * 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)
    }
}

这将使测试看起来像:

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

@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
}

这将使测试看起来像:

class ViewModelTest {

    @get:Rule
    val rule = InstantTaskExecutorRule()

    @Test
    fun isFirstLaunchTest() {

        viewModel.someMethodThatAffectsFirstLaunchLiveData()

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

    }
}

这些选项均取自架构蓝图的反应性分支.

这篇关于测试 LiveData 转换?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆