You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

测试用例失败: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 08:52:08