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

Kotlin测试返回Arrangement.Vertical的Lambda函数遇错误求助

问题排查:PairViewModel.isBluetoothEnable测试中的类型转换与MockK签名匹配失败

问题背景

测试PairViewModel中的isBluetoothEnable函数时遇到连续问题:

  1. 初始测试代码触发ClassCastException类型转换错误
  2. 参考方案调整后,出现MockK签名匹配失败问题

函数逻辑:isBluetoothEnable接收两个返回Arrangement.Vertical类型的Lambda参数,根据isBluetoothEnabled状态调用对应Lambda并返回结果。

相关代码示例

ViewModel核心函数

fun isBluetoothEnable(
    enabled: () -> Arrangement.Vertical,
    disabled: () -> Arrangement.Vertical
): Arrangement.Vertical {
    return if (isBluetoothEnabled) enabled() else disabled()
}

触发ClassCastException的测试代码

@Test
fun `test bluetooth enabled state`() {
    val viewModel = spyk(PairViewModel())
    viewModel.isBluetoothEnabled = true
    
    val result = viewModel.isBluetoothEnable(
        mockk(), // 未指定Lambda类型,MockK生成的代理无法转换
        mockk()
    )
    
    assertThat(result).isEqualTo(Arrangement.Top)
}

触发MockK签名匹配失败的测试代码

@Test
fun `test bluetooth enabled state`() {
    val viewModel = spyk(PairViewModel())
    viewModel.isBluetoothEnabled = true
    
    val enabledLambda = mockk<() -> Arrangement.Vertical>()
    every { enabledLambda() } returns Arrangement.Top
    
    val result = viewModel.isBluetoothEnable(enabledLambda, mockk())
    
    verify { enabledLambda() } // 签名匹配失败
    assertThat(result).isEqualTo(Arrangement.Top)
}

错误日志

ClassCastException日志

java.lang.ClassCastException: io.mockk.proxy.jvm.advice.JvmMockKProxyInterceptor cannot be cast to androidx.compose.foundation.layout.Arrangement$Vertical

MockK签名匹配失败日志

io.mockk.MockKException: no answer found for: Function0#call()

问题原因与解决方案

1. ClassCastException原因

直接用mockk()创建Lambda参数时,MockK默认生成的代理对象无法自动适配() -> Arrangement.Vertical类型,必须明确指定Lambda的类型参数。

2. MockK签名匹配失败原因

  • 使用spyk包装ViewModel后,内部调用Lambda时会触发MockK的代理拦截,导致实际调用的签名与验证逻辑不匹配
  • 若Lambda被ViewModel内部逻辑二次处理,MockK的默认验证规则无法识别实际调用路径

修复后的测试代码

方案1:无依赖场景直接实例化ViewModel

@Test
fun `test bluetooth enabled returns correct arrangement`() {
    val viewModel = PairViewModel().apply {
        isBluetoothEnabled = true
    }
    
    val expectedArrangement = Arrangement.Top
    val result = viewModel.isBluetoothEnable(
        { expectedArrangement },
        { Arrangement.Bottom }
    )
    
    assertThat(result).isEqualTo(expectedArrangement)
}

方案2:带依赖场景的MockK正确用法

@Test
fun `test bluetooth enabled with mocked dependencies`() {
    val mockDependency = mockk<SomeDependency>()
    every { mockDependency.someMethod() } returns Unit
    
    // 开启私有调用记录,确保Lambda调用被正确捕获
    val viewModel = spyk(PairViewModel(mockDependency), recordPrivateCalls = true)
    viewModel.isBluetoothEnabled = true
    
    val enabledLambda = { Arrangement.Top }
    val disabledLambda = { Arrangement.Bottom }
    
    val result = viewModel.isBluetoothEnable(enabledLambda, disabledLambda)
    
    assertThat(result).isEqualTo(Arrangement.Top)
    
    // 使用捕获器验证Lambda是否被调用
    val captor = slot<() -> Arrangement.Vertical>()
    verify { viewModel.isBluetoothEnable(capture(captor), any()) }
    assertThat(captor.called()).isTrue()
}

关键注意点

  • 无复杂依赖的ViewModel测试,优先直接实例化而非spyk,避免MockK代理带来的签名冲突
  • 模拟Lambda时,尽量使用具体函数实现(而非mockk),仅在需要验证调用次数时再用MockK模拟
  • 必须用MockK模拟Lambda时,需确保类型参数明确,且验证逻辑匹配实际调用路径

内容的提问来源于stack exchange,提问作者Compose Learner

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 20:15:33