Android 测试LiveData转换?
我已经使用Android架构组件和反应式方法构建了一个启动屏幕。 我从首选项LiveData对象返回乐趣isFirstLaunchLD():SharedReferencesLiveData。 我有一个ViewModel,它将LiveData传递给视图并更新首选项Android 测试LiveData转换?,android,android-testing,android-architecture-components,android-livedata,android-jetpack,Android,Android Testing,Android Architecture Components,Android Livedata,Android Jetpack,我已经使用Android架构组件和反应式方法构建了一个启动屏幕。 我从首选项LiveData对象返回乐趣isFirstLaunchLD():SharedReferencesLiveData。 我有一个ViewModel,它将LiveData传递给视图并更新首选项 val isFirstLaunch = Transformations.map(preferences.isFirstLaunchLD()) { isFirstLaunch -> if (isFirstLaunch) {
val isFirstLaunch = Transformations.map(preferences.isFirstLaunchLD()) { isFirstLaunch ->
if (isFirstLaunch) {
preferences.isFirstLaunch = false
}
isFirstLaunch
}
在我的片段中,我从ViewModel观察LiveData
viewModel.isFirstLaunch.observe(this, Observer { isFirstLaunch ->
if (isFirstLaunch) {
animationView.playAnimation()
} else {
navigateNext()
}
})
我现在想测试我的ViewModel,看看isFirstLaunch是否正确更新。我如何测试它?我是否正确地分隔了所有层?您将在这个示例代码上编写什么样的测试?这取决于您的SharedReferencesLiveData的功能 如果SharedPreferencesLiveData包含特定于Android的类,您将无法正确测试它,因为JUnit将无法访问特定于Android的类 另一个问题是,为了能够观察LiveData,您需要某种生命周期所有者。(原始邮政编码中包含此信息。) 在单元测试中,“this”可以简单地替换为以下内容:
private fun lifecycle(): Lifecycle {
val lifecycle = LifecycleRegistry(Mockito.mock(LifecycleOwner::class.java))
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
return lifecycle
}
然后按以下方式使用:
@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立即执行,而不是在需要时执行
我是否正确地分隔了所有层
这些层似乎合理地分开了。逻辑在ViewModel中,您可以使用它
您会在这个示例代码上编写什么样的测试
测试ViewModel时,您可以在此代码上编写指令插入或纯单元测试。对于单元测试,您可能需要弄清楚如何对首选项进行双重测试,以便关注isFirstLaunch/map行为。一个简单的方法是在ViewModel中通过一个伪偏好测试
我如何测试它
我写了一篇关于测试LiveData转换的简介,请继续阅读
测试LiveData转换
Tl;DR您可以测试LiveData转换,只需确保观察到转换的结果
事实1:LiveData在未观察到数据的情况下不会发出数据。LiveData的“”旨在避免额外的工作。LiveData知道其观察者(通常是活动/片段)所处的生命周期状态。这使LiveData能够知道屏幕上是否有任何东西在观察它。如果未观察到LiveData或其观察者在屏幕外,则不会触发观察者(不会调用观察者的onChanged方法)。这是很有用的,因为它可以防止您进行额外的工作,例如“更新/显示”屏幕外的片段
事实2:必须观察转换生成的LiveData才能触发转换。要触发转换,必须观察结果LiveData(在本例中为isFirstLaunch)。同样,如果没有观察,LiveData观察者不会被触发,转换也不会被触发
当您对ViewModel进行单元测试时,您不应该或不需要访问片段/活动。如果不能以正常的方式设置观察者,如何进行单元测试
事实3:在您的测试中,您不需要LifecycleOwner来观察LiveData,您可以使用ObserveForver您不需要lifecycle Owner来测试LiveData。这是令人困惑的,因为通常在测试之外(即在生产代码中),您将使用类似于活动或片段的
在测试中,您可以使用LiveData方法在没有生命周期所有者的情况下观察。这个观察者“总是”观察,没有屏幕上/屏幕下的概念,因为没有LifecycleOwner。因此,必须使用removeObserver(观察者)手动删除观察者
综上所述,您可以使用ObserveForRever测试转换代码:
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)
}
}
}
这将使测试看起来像:
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)
}
}
}
class ViewModelTest {
@get:Rule
val rule = InstantTaskExecutorRule()
@Test
fun isFirstLaunchTest() {
viewModel.someMethodThatAffectsFirstLaunchLiveData()
// getOrAwaitValue using the OUTPUT livedata
assertEquals(viewModel.isFirstLaunch.getOrAwaitValue(), 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
}
这些选项均取自。您是否找到了测试转换的正确方法?如果每次测试后创建新的ViewModel,为什么需要从observe forever中删除观察者?收集viewmodel后,垃圾收集器不应该收集观察者吗?
class ViewModelTest {
@get:Rule
val rule = InstantTaskExecutorRule()
@Test
fun isFirstLaunchTest() {
viewModel.someMethodThatAffectsFirstLaunchLiveData()
// getOrAwaitValue using the OUTPUT livedata
assertEquals(viewModel.isFirstLaunch.getOrAwaitValue(), true)
}
}