如何确保在 Android 单元测试中调用 ViewModel#onCleared?
How to ensure ViewModel#onCleared is called in an Android unit test?
这是我的 MWE 测试 class,它依赖于 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() {}
}
注意:该方法在superclass ViewModel
.
中被保护
我想验证 MyViewModel#onCleared
调用了 Object#function
。上面的代码通过反射完成了这个。我的问题是:我能否以某种方式 运行 或模拟 Android 系统以便调用 onCleared
方法,这样我就不需要反射?
来自 onCleared
JavaDoc:
This method will be called when this ViewModel is no longer used and will be destroyed.
所以,换句话说,我如何创建这种情况,以便我知道 onCleared
被调用并且我可以验证其行为?
TL;DR
在此答案中,Robolectric 用于让 Android 框架在您的 ViewModel
上调用 onCleared
。这种测试方式比使用反射(如问题)慢,并且取决于 Robolectric 和 Android 框架。取舍取决于您。
正在查看 Android 的来源...
...你可以看到 ViewModel#onCleared
只在 ViewModelStore
中被调用(对于你自己的 ViewModels
)。这是视图模型的存储 class,由 ViewModelStoreOwner
classes 拥有,例如FragmentActivity
。那么,ViewModelStore
什么时候在您的 ViewModel
上调用 onCleared
?
它必须存储你的ViewModel
,然后必须清除存储(你不能自己做)。
当您使用 ViewModelProviders.of(FragmentActivity activity).get(Class<T> modelClass)
get
您的 ViewModel
时,您的视图模型由 ViewModelProvider
存储,其中 T
是您的视图模型 class.它存储在 FragmentActivity
的 ViewModelStore
中。
例如,当您的片段 activity 被销毁时,商店就会清空。这是一堆到处都是的链式调用,但基本上是:
- 有一个
FragmentActivity
.
- 使用
ViewModelProviders#of
获取它的 ViewModelProvider
。
- 使用
ViewModelProvider#get
获取您的 ViewModel
。
- 摧毁你的activity。
现在,应该在您的视图模型上调用 onCleared
。让我们使用 Robolectric 4、JUnit 4、MockK 1.9 对其进行测试:
- 将
@RunWith(RobolectricTestRunner::class)
添加到您的测试中 class。
- 使用
Robolectric.buildActivity(FragmentActivity::class.java)
创建一个 activity 控制器
- 在控制器上使用
setup
初始化 activity,这允许它被销毁。
- 通过控制器的
get
方法获取activity。
- 按照上述步骤获取视图模型。
- 使用控制器上的
destroy
摧毁 activity。
- 验证
onCleared
的行为。
完整示例class...
...基于问题的示例:
@RunWith(RobolectricTestRunner::class)
class ViewModelOnClearedTest {
@Test
fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
val controller = Robolectric.buildActivity(FragmentActivity::class.java).setup()
ViewModelProviders.of(controller.get()).get(MyViewModel::class.java)
controller.destroy()
verify { Object.function() }
}
}
class MyViewModel : ViewModel() {
override fun onCleared() = Object.function()
}
object Object {
fun function() {}
}
我刚刚为 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
}
在 kotlin 中,您可以使用 public
覆盖受保护的可见性,然后从测试中调用它。
class MyViewModel: ViewModel() {
public override fun onCleared() {
///...
}
}
对于 Java,如果您在与 ViewModel class(此处为 MyViewModel)相同的包(在测试目录中)中创建测试 class,那么您可以从测试 class 中调用 onCleared
方法;因为受保护的方法也是包私有的。
这是我的 MWE 测试 class,它依赖于 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() {}
}
注意:该方法在superclass ViewModel
.
我想验证 MyViewModel#onCleared
调用了 Object#function
。上面的代码通过反射完成了这个。我的问题是:我能否以某种方式 运行 或模拟 Android 系统以便调用 onCleared
方法,这样我就不需要反射?
来自 onCleared
JavaDoc:
This method will be called when this ViewModel is no longer used and will be destroyed.
所以,换句话说,我如何创建这种情况,以便我知道 onCleared
被调用并且我可以验证其行为?
TL;DR
在此答案中,Robolectric 用于让 Android 框架在您的 ViewModel
上调用 onCleared
。这种测试方式比使用反射(如问题)慢,并且取决于 Robolectric 和 Android 框架。取舍取决于您。
正在查看 Android 的来源...
...你可以看到 ViewModel#onCleared
只在 ViewModelStore
中被调用(对于你自己的 ViewModels
)。这是视图模型的存储 class,由 ViewModelStoreOwner
classes 拥有,例如FragmentActivity
。那么,ViewModelStore
什么时候在您的 ViewModel
上调用 onCleared
?
它必须存储你的ViewModel
,然后必须清除存储(你不能自己做)。
当您使用 ViewModelProviders.of(FragmentActivity activity).get(Class<T> modelClass)
get
您的 ViewModel
时,您的视图模型由 ViewModelProvider
存储,其中 T
是您的视图模型 class.它存储在 FragmentActivity
的 ViewModelStore
中。
例如,当您的片段 activity 被销毁时,商店就会清空。这是一堆到处都是的链式调用,但基本上是:
- 有一个
FragmentActivity
. - 使用
ViewModelProviders#of
获取它的ViewModelProvider
。 - 使用
ViewModelProvider#get
获取您的ViewModel
。 - 摧毁你的activity。
现在,应该在您的视图模型上调用 onCleared
。让我们使用 Robolectric 4、JUnit 4、MockK 1.9 对其进行测试:
- 将
@RunWith(RobolectricTestRunner::class)
添加到您的测试中 class。 - 使用
Robolectric.buildActivity(FragmentActivity::class.java)
创建一个 activity 控制器
- 在控制器上使用
setup
初始化 activity,这允许它被销毁。 - 通过控制器的
get
方法获取activity。 - 按照上述步骤获取视图模型。
- 使用控制器上的
destroy
摧毁 activity。 - 验证
onCleared
的行为。
完整示例class...
...基于问题的示例:
@RunWith(RobolectricTestRunner::class)
class ViewModelOnClearedTest {
@Test
fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
val controller = Robolectric.buildActivity(FragmentActivity::class.java).setup()
ViewModelProviders.of(controller.get()).get(MyViewModel::class.java)
controller.destroy()
verify { Object.function() }
}
}
class MyViewModel : ViewModel() {
override fun onCleared() = Object.function()
}
object Object {
fun function() {}
}
我刚刚为 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
}
在 kotlin 中,您可以使用 public
覆盖受保护的可见性,然后从测试中调用它。
class MyViewModel: ViewModel() {
public override fun onCleared() {
///...
}
}
对于 Java,如果您在与 ViewModel class(此处为 MyViewModel)相同的包(在测试目录中)中创建测试 class,那么您可以从测试 class 中调用 onCleared
方法;因为受保护的方法也是包私有的。