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

如何编写Kotlin单元测试测试内部调用其他函数的方法

问题根因

if分支无法进入的直接原因是执行到判断逻辑时,inventoryInfo始终为null或空集合,常见触发场景:

  • 未正确Mock静态工具类UtilityFunctions的方法,测试运行时要么走真实逻辑拿到不符合预期的返回值,要么Mock框架默认返回null
  • 传入的测试入参message不符合工具类解析规则,即使走真实解析逻辑也拿不到非空的inventoryInfo
  • 前序步骤(sqsQPayloadExtractor调用、返回值取索引)抛出异常,被外层catch吞掉,根本没走到if判断逻辑
分步解决方法

1. 选择适配Kotlin的静态Mock方案

Kotlin中的静态方法、顶层函数、伴生对象方法无法用原生Mockito直接Mock,优先选Kotlin专属测试框架MockK,对静态方法、空安全、扩展函数的适配更完善;如果坚持用Mockito,需要引入mockito-inline依赖(mockito-core 3.4+版本已内置该能力)支持静态Mock。

  • MockK静态Mock写法:
// 测试类中声明静态Mock
mockkStatic(UtilityFunctions::class)
// 单测执行完成后解绑静态Mock,避免影响其他用例
@AfterEach
fun tearDown() {
    unmockkStatic(UtilityFunctions::class)
}
  • Mockito-inline静态Mock写法:
// 用try-with-resources自动关闭静态Mock
try (MockedStatic<UtilityFunctions> mockedStatic = Mockito.mockStatic(UtilityFunctions::class.java)) {
    // 此处编写Mock规则与测试执行逻辑
}

2. 按分支场景构造Mock返回值

要进入if分支,必须保证extractInventoryType返回非空且长度至少为2的集合——因为分支内直接取了inventoryInfo[0]和inventoryInfo[1],集合长度不足会抛出数组越界异常。
覆盖if分支的核心Mock逻辑参考(以MockK为例):

@Test
fun `process when inventoryInfo is valid should call startProcess`() {
    // 构造测试数据
    val testMsg = "test-sqs-raw-message"
    val mockSqsPayload = listOf("test-payload-body", "test-bucket-name")
    val mockInventoryInfo = listOf("inventory-type", "inventory-file-path")

    // 初始化依赖与待测试类
    val mockProcessService = mockk<TransformerProcessService>()
    val testInstance = YourProcessorClass(mockProcessService) // 替换为你自己的待测试类名

    // 配置Mock规则
    every { UtilityFunctions.sqsQPayloadExtractor(testMsg) } returns mockSqsPayload
    every { UtilityFunctions.extractInventoryType(mockSqsPayload[0]) } returns mockInventoryInfo
    every { mockProcessService.startProcess(any(), any(), any(), any()) } just Runs

    // 执行待测试方法
    testInstance.process(testMsg)

    // 验证if分支逻辑确实触发
    verify(exactly = 1) {
        mockProcessService.startProcess(
            mockSqsPayload[0],
            mockInventoryInfo[0],
            mockInventoryInfo[1],
            mockSqsPayload[1]
        )
    }
}

要覆盖else分支,只需要把extractInventoryType的Mock返回值改为null或者空集合即可:

every { UtilityFunctions.extractInventoryType(mockSqsPayload[0]) } returns null
// 或 returns emptyList()

3. 常见避坑点

  • 不要遗漏sqsQPayloadExtractor的Mock:如果该方法Mock失败返回null,后续取sqsPayloadInfo[0]会直接触发空指针,被外层catch块捕获,根本到不了if判断
  • 注意Kotlin空安全规则:如果extractInventoryType源码定义的返回值是不可空类型(没有带?修饰),Mock时强行返回null会直接抛空指针,先确认方法签名的可空性
  • 你当前代码的catch块会吞掉所有异常:如果测试执行后既没走if也没走else,先在catch块加断点,大概率是前序逻辑抛了异常(比如数组越界、Mock未生效、依赖未注入)
  • 长期优化建议:尽量避免直接静态调用工具类方法,改为通过构造函数注入依赖,后续测试不需要依赖静态Mock能力,代码可测试性更高

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 09:33:30