Android Kotlin单元测试中DateUtils.getRelativeTimeSpanString抛空指针异常
解决方案
核心原因
你遇到的问题本质是Robolectric对DateUtils.getRelativeTimeSpanString()这个Android系统静态方法的模拟存在缺陷,导致在本地单元测试环境中调用时触发NullPointerException。插桩测试在真实Android环境运行,系统方法能正常执行所以不会报错,而@JvmStatic注解仅用于处理Kotlin静态方法的Java兼容性,和这个系统方法的NPE问题无关。
具体解决办法
方法1:Mock系统静态方法(快速解决)
因为工具类直接依赖Android系统类的静态方法,当Robolectric无法正确模拟时,直接用Mockito来Mock这个静态方法即可:
- 确保项目引入Mockito-Kotlin和Mockito-inline依赖(用于支持静态方法Mock)
- 在测试类中编写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系统类:
- 定义一个接口:
interface TimeSpanProvider { fun getRelativeTimeSpanString(time: Long, now: Long, minResolution: Long): CharSequence }
- 实现真实的Provider(在主代码中):
class AndroidTimeSpanProvider : TimeSpanProvider { override fun getRelativeTimeSpanString(time: Long, now: Long, minResolution: Long): CharSequence { return DateUtils.getRelativeTimeSpanString(time, now, minResolution) } }
- 修改你的
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() } }
- 测试时直接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
相关产品推荐
相关产品推荐

