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

Android Kotlin单元测试中DateUtils.getRelativeTimeSpanString抛空指针异常

解决方案

核心原因

你遇到的问题本质是Robolectric对DateUtils.getRelativeTimeSpanString()这个Android系统静态方法的模拟存在缺陷,导致在本地单元测试环境中调用时触发NullPointerException。插桩测试在真实Android环境运行,系统方法能正常执行所以不会报错,而@JvmStatic注解仅用于处理Kotlin静态方法的Java兼容性,和这个系统方法的NPE问题无关。

具体解决办法

方法1:Mock系统静态方法(快速解决)

因为工具类直接依赖Android系统类的静态方法,当Robolectric无法正确模拟时,直接用Mockito来Mock这个静态方法即可:

  1. 确保项目引入Mockito-Kotlin和Mockito-inline依赖(用于支持静态方法Mock)
  2. 在测试类中编写Mock逻辑:
import org.junit.jupiter.api.Test
import org.mockito.Mockito.mockStatic
import android.text.format.DateUtils
import org.junit.jupiter.api.Assertions.assertEquals
import org.mockito.kotlin.anyLong

class TimeSpanUtilsTest {

    @Test
    fun testRelativeTimeSpan() {
        // Mock DateUtils类的静态方法
        mockStatic(DateUtils::class.java).use { mock ->
            // 指定调用目标方法时返回预期结果
            mock.`when`(
                DateUtils.getRelativeTimeSpanString(anyLong(), anyLong(), anyLong())
            ).thenReturn("5分钟前")
            
            // 调用你的工具类方法
            val result = TimeSpanUtils.yourMethodToTest()
            
            // 断言结果符合预期
            assertEquals("5分钟前", result)
        }
    }
}

方法2:封装系统调用(更优雅的长期方案)

通过依赖倒置原则,把系统方法的调用封装成可替换的接口,避免直接依赖Android系统类:

  1. 定义一个接口:
interface TimeSpanProvider {
    fun getRelativeTimeSpanString(time: Long, now: Long, minResolution: Long): CharSequence
}
  1. 实现真实的Provider(在主代码中):
class AndroidTimeSpanProvider : TimeSpanProvider {
    override fun getRelativeTimeSpanString(time: Long, now: Long, minResolution: Long): CharSequence {
        return DateUtils.getRelativeTimeSpanString(time, now, minResolution)
    }
}
  1. 修改你的TimeSpanUtils,让它依赖这个接口:
class TimeSpanUtils(private val timeSpanProvider: TimeSpanProvider = AndroidTimeSpanProvider()) {
    fun yourMethodToTest(): String {
        // 调用接口方法而非直接调用DateUtils
        val span = timeSpanProvider.getRelativeTimeSpanString(1620000000000, 1620003000000, DateUtils.MINUTE_IN_MILLIS)
        return span.toString()
    }
}
  1. 测试时直接Mock这个接口:
import org.junit.jupiter.api.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.junit.jupiter.api.Assertions.assertEquals
import org.mockito.kotlin.any

class TimeSpanUtilsTest {

    @Test
    fun testRelativeTimeSpan() {
        val mockProvider = mock<TimeSpanProvider>()
        whenever(mockProvider.getRelativeTimeSpanString(any(), any(), any())).thenReturn("5分钟前")
        
        val utils = TimeSpanUtils(mockProvider)
        val result = utils.yourMethodToTest()
        
        assertEquals("5分钟前", result)
    }
}

方法3:调整Robolectric配置

如果不想用Mock,可以尝试:

  • 升级到最新版本的Robolectric,新版本通常会修复更多系统API的模拟问题
  • 在测试类上添加@Config(sdk = [Build.VERSION_CODES.TIRAMISU])(指定与目标SDK匹配的版本),确保Robolectric模拟的系统环境与代码兼容

内容的提问来源于stack exchange,提问作者Peter G. Williams

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 11:57:54