如何确保在Android单元测试中调用ViewModel#onCleared?

时间:2019-01-09 17:40:10

标签: android unit-testing android-testing android-architecture-components android-viewmodel

这是我的MWE测试类,它取决于AndroidX,JUnit 4和MockK 1.9:

class ViewModelOnClearedTest {
    @Test
    fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
        MyViewModel::class.members
            .single { it.name == "onCleared" }
            .apply { isAccessible = true }
            .call(MyViewModel())

        verify { Object.function() }
    }
}

class MyViewModel : ViewModel() {
    override fun onCleared() = Object.function()
}

object Object {
    fun function() {}
}

请注意:该方法在超类ViewModel中受保护。

我想验证MyViewModel#onCleared是否呼叫Object#function。上面的代码是通过反射实现的。我的问题是:我可以以某种方式运行或模拟Android系统,以便调用onCleared方法,这样就不需要反射了吗?

onCleared JavaDoc:

  

当不再使用此ViewModel时,将调用此方法。

换句话说,如何创建这种情况以使我知道onCleared被呼叫并且可以验证其行为?

3 个答案:

答案 0 :(得分:6)

在kotlin中,我发现可以使用public覆盖受保护的可见性,然后可以从测试中调用它。

class MyViewModel: ViewModel() {
    public override fun onCleared() {
        ///...
    }
}

答案 1 :(得分:0)

TL; DR

在此答案中,Robolectric用于让Android框架在您的onCleared上调用ViewModel。这种测试方法比使用反射(像在问题中一样)要慢,并且取决于Robolectric和Android框架。权衡由您决定。


正在查看Android的来源...

...您可以看到ViewModel#onCleared仅在ViewModelStore中被调用(对于您自己的ViewModels)。这是视图模型的存储类,由ViewModelStoreOwner类拥有,例如FragmentActivity。那么,ViewModelStore何时在您的onCleared上调用ViewModel

它必须存储您的ViewModel,然后必须清除存储(您不能自己做)。

当您使用ViewModelProvider get ViewModel时,ViewModelProviders.of(FragmentActivity activity).get(Class<T> modelClass)存储您的视图模型,其中T是您的视图模型类。它将其存储在ViewModelStore的{​​{1}}中。

例如,当您的片段活动被销毁时,商店就很清楚。到处都是一连串的链接呼叫,但基本上是:

  1. 拥有一个FragmentActivity
  2. 使用FragmentActivity获取其ViewModelProvider
  3. 使用ViewModelProviders#of获取ViewModel
  4. 破坏你的活动。

现在,应该在视图模型上调用ViewModelProvider#get。让我们使用Robolectric 4,JUnit 4,MockK 1.9进行测试:

  1. onCleared添加到测试班。
  2. 使用@RunWith(RobolectricTestRunner::class)
  3. 创建活动控制器
  4. 使用控制器上的Robolectric.buildActivity(FragmentActivity::class.java)初始化活动,这可以将其销毁。
  5. 使用控制器的setup方法获取活动。
  6. 通过上述步骤获取视图模型。
  7. 使用控制器上的get破坏活动。
  8. 验证destroy的行为。

完整的示例类...

...根据问题的示例:

onCleared

答案 2 :(得分:0)

我刚刚为ViewModel创建了此扩展名:

/**
 * Will create new [ViewModelStore], add view model into it using [ViewModelProvider]
 * and then call [ViewModelStore.clear], that will cause [ViewModel.onCleared] to be called
 */
fun ViewModel.callOnCleared() {
    val viewModelStore = ViewModelStore()
    val viewModelProvider = ViewModelProvider(viewModelStore, object : ViewModelProvider.Factory {

        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T = this@callOnCleared as T
    })
    viewModelProvider.get(this@callOnCleared::class.java)

    //Run 2
    viewModelStore.clear()//To call clear() in ViewModel
}