测试用例失败:ClassCastException无法转换ViewModel子类为SecondChildViewModel
解决父子Activity/ViewModel结构下的ClassCastException问题
问题背景
ViewModel继承关系:
- FirstSecondChildCancellationViewModel → ParentViewModel
- SecondChildCancellationViewModel → ParentViewModel
Activity继承关系:
- FirstChildCancellationActivity → ParentActivity
- SecondChildCancellationActivity → ParentActivity
测试时触发ClassCastException,提示无法将ViewModel$Subclass1转为SecondChildCancellationViewModel,推测是代码逻辑错误获取到父ViewModel实例,导致类型转换失败。
测试代码
@Before初始化方法
@Before fun setUp() { binding = mockk() val intent = Intent() val bundle = Bundle() bundle.putParcelable(EXTRA_HEADER_LAYOUT, myCancelModel) intent.putExtras(bundle) subjectController = Robolectric.buildActivity( SecondChildCancellationActivity::class.java, intent ).create() // 测试在此处失败 subject = subjectController.get() mockkStatic(ViewModelProviders::class) val mockViewModelProvider = mockkClass(ViewModelProvider::class) every { ViewModelProviders.of(any<FragmentActivity>(), any()) } returns mockViewModelProvider every { mockViewModelProvider.get(any<Class<SecondChildCancellationViewModel>>()) } returns mockViewModel }
测试用例
@Test fun `verify onCreate init is happening and status is displaying in header`() { subjectController.visible().get() assert(subject.cancelId).isEqualTo(myCancelModel.cancelId) assert(binding.statusTextView.status).isEqualTo(myCancelModel.statusText) }
报错信息
Caused by: java.lang.ClassCastException: class androidx.lifecycle.ViewModel$Subclass1 cannot be cast to class com.company.project.cancellation.SecondChildCancellationViewModel (androidx.lifecycle.ViewModel$Subclass1 and com.company.project.cancellation.SecondChildCancellationViewModel are in unnamed module of loader org.robolectric.internal.AndroidSandbox$SdkSandboxClassLoader @12e24679) at com.company.project.cancellation.SecondChildCancellationActivity.retrieveViewModel(SecondChildCancellationActivity.kt:47)
解决方案
1. 调整Mock时机,提前配置ViewModelProviders
当前代码在Activity执行create()后才MockViewModelProviders,此时Activity的onCreate已经触发,真实的ViewModelProvider会创建默认的空ViewModel实例(即报错中的ViewModel$Subclass1),后续Mock无法覆盖这个时机。
修改setUp方法,先完成Mock配置,再创建Activity并执行初始化:
@Before fun setUp() { binding = mockk() val intent = Intent() val bundle = Bundle() bundle.putParcelable(EXTRA_HEADER_LAYOUT, myCancelModel) intent.putExtras(bundle) // 先完成ViewModelProviders的Mock配置 mockkStatic(ViewModelProviders::class) val mockViewModelProvider = mockkClass(ViewModelProvider::class) every { ViewModelProviders.of(any<FragmentActivity>(), any()) } returns mockViewModelProvider every { mockViewModelProvider.get(SecondChildCancellationViewModel::class.java) } returns mockViewModel // 再创建Activity并执行create subjectController = Robolectric.buildActivity( SecondChildCancellationActivity::class.java, intent ).create() subject = subjectController.get() }
2. 检查Activity的ViewModel获取逻辑
确认SecondChildCancellationActivity的retrieveViewModel方法明确指定获取子类ViewModel类型,避免复用父Activity的逻辑拿到父ViewModel:
// 正确的子类ViewModel获取示例 private fun retrieveViewModel(): SecondChildCancellationViewModel { return ViewModelProviders.of(this).get(SecondChildCancellationViewModel::class.java) }
如果父Activity有通用的ViewModel获取逻辑,子类必须重写该方法并指定自身对应的ViewModel类型。
3. 验证Mock实例的类型匹配
确保测试中初始化的mockViewModel是SecondChildCancellationViewModel的Mock实例,而非父类ViewModel:
// 初始化时指定正确的子类类型 private val mockViewModel: SecondChildCancellationViewModel = mockk()
修改完成后重新运行测试,即可避免类型转换异常,确保测试获取到正确的子ViewModel实例。
内容的提问来源于stack exchange,提问作者AndyBoy
相关产品推荐
相关产品推荐

