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

如何用Mockito模拟并验证Android ICU的NumberFormatter

问题解决方案

方案A:解决Measure.equals未mock的异常

问题根源:Android单元测试环境中,android.icu.util.Measure属于未被完全实现的SDK类,直接在verify中比较Measure对象会触发未mock的equals方法,导致异常。可以通过以下两种方式解决:

方式1:用ArgumentCaptor捕获参数验证属性

@Test
fun testA() {
    val formattedNumber = mock<FormattedNumber> {
        on { toString() } doReturn "X units"
    }
    val numberFormatter = mock<LocalizedNumberFormatter> {
        on { unitWidth(any()) } doReturn it
        on { format(any<Measure>()) } doReturn formattedNumber
    }

    val formatter = CustomFormatter(numberFormatter)
    formatter.formatA("stuff")

    // 捕获传入format方法的Measure参数
    val measureCaptor = ArgumentCaptor.forClass(Measure::class.java)
    verify(numberFormatter).format(measureCaptor.capture())
    
    // 验证捕获到的Measure的数值和单位是否正确
    val capturedMeasure = measureCaptor.value
    assertEquals(10L, capturedMeasure.number)
    assertEquals(MeasureUnit.SECOND, capturedMeasure.unit)
}

方式2:自定义ArgumentMatcher匹配参数

@Test
fun testA() {
    val formattedNumber = mock<FormattedNumber> {
        on { toString() } doReturn "X units"
    }
    val numberFormatter = mock<LocalizedNumberFormatter> {
        on { unitWidth(any()) } doReturn it
        on { format(any<Measure>()) } doReturn formattedNumber
    }

    val formatter = CustomFormatter(numberFormatter)
    formatter.formatA("stuff")

    // 自定义匹配逻辑,检查Measure的内部属性
    verify(numberFormatter).format(argThat {
        it.number == 10L && it.unit == MeasureUnit.SECOND
    })
}

方案B:解决NullPointerException问题

NPE的核心原因是链式调用的mock返回错误:LocalizedNumberFormatter的unitWidth、unit方法实际返回的是构建器实例(而非自身),但你的mock让这些方法返回了原LocalizedNumberFormatter对象,导致后续方法调用出现类型不兼容或null。

正确的mock方式需要拆分链式调用的每个节点:

@Test
fun testB() {
    val formattedNumber = mock<FormattedNumber> {
        on { toString() } doReturn "X units"
    }
    // mock设置unit后的构建器,负责处理后续format调用
    val unitBuilder = mock<LocalizedNumberFormatter> {
        on { unit(any<MeasureUnit>()) } doReturn it
        on { format(any<Long>()) } doReturn formattedNumber
    }
    // 初始mock,unitWidth返回设置unit的构建器
    val numberFormatter = mock<LocalizedNumberFormatter> {
        on { unitWidth(any<NumberFormatter.UnitWidth>()) } doReturn unitBuilder
    }

    val formatter = CustomFormatter(numberFormatter)
    formatter.formatB("stuff")

    verify(numberFormatter).unitWidth(NumberFormatter.UnitWidth.FULL_NAME)
    verify(unitBuilder).unit(MeasureUnit.SECOND)
    verify(unitBuilder).format(10L)
}

额外注意事项

  • Android单元测试中,ICU相关类属于SDK stub类,没有实际实现,避免直接比较对象,优先验证内部属性
  • Mock链式调用时,必须严格遵循原API的返回类型,每个方法返回对应的后续构建器实例,不能一概返回原mock对象

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 17:22:39