Mockito模拟Instant计算Duration时遇空指针及反射访问错误问题
解决Mockito模拟Instant时Duration计算报错及反射权限问题
问题场景
使用Mockito 4.7.0静态模拟Instant.now()返回固定时间,调用Duration.between()时触发错误:Cannot read field "seconds" because "end" is null;尝试添加Mockito.CALLS_REAL_METHODS调用真实方法后,又出现反射访问权限错误:class org.mockito.internal.util.reflection.ReflectionMemberAccessor cannot access a member of class java.time.Instant (in module java.base) with modifiers "private static"。
问题原因
- 静态mock整个
Instant类时,Mockito默认会mock所有未显式stub的方法,导致Duration.between()依赖的Instant内部方法返回默认值(如null),触发空指针错误。 java.time.Instant属于JDK核心模块java.base,模块系统限制了反射对私有静态成员的访问,因此添加CALLS_REAL_METHODS会触发权限异常。
解决方案
方案1:避免静态mock,封装时间获取逻辑(推荐)
将获取当前时间的逻辑封装为可注入的依赖(如Clock),测试时使用固定时钟替代静态方法调用,彻底规避静态mock的问题。
修改业务代码:
import java.time.{Clock, Duration, Instant} class ClassToTest(clock: Clock = Clock.systemUTC()) { def run(): Duration = { val now = Instant.now(clock) val now1 = Instant.now(clock) Duration.between(now1, now) } }
测试代码:
import org.junit.Test import java.time.{Clock, Instant, ZoneId} @Test def test_duration(): Unit = { val fixedInstant = Instant.parse("2022-08-31T08:55:48.200Z") val fixedClock = Clock.fixed(fixedInstant, ZoneId.of("UTC")) val testee = new ClassToTest(fixedClock) val result = testee.run() // 验证结果:两次获取的时间相同,Duration应为0 assert(result.isZero) }
方案2:正确配置静态mock(仅作兼容参考)
若必须使用静态mock,需确保仅stubInstant.now(),同时让其他方法调用真实实现,且规避JDK模块权限限制:
- 在测试启动的JVM参数中添加:
--add-opens java.base/java.time=ALL-UNNAMED
- 修改测试代码:
import org.junit.Test import org.mockito.Mockito.{mockStatic, CALLS_REAL_METHODS} import java.time.{Duration, Instant} @Test def test_duration(): Unit = { val testee = new ClassToTest() val timeMocked = Instant.parse("2022-08-31T08:55:48.200Z") val mock = mockStatic(classOf[Instant], CALLS_REAL_METHODS) try { mock.when(() => Instant.now()).thenReturn(timeMocked) val result = testee.run() assert(result.isZero) } finally { if (mock != null) mock.close() } }
说明
方案1遵循依赖注入原则,代码更易测试和维护,是推荐的最佳实践;方案2需修改JVM参数,且静态mock可能带来其他潜在问题,仅在无法修改业务代码时使用。
内容的提问来源于stack exchange,提问作者Kewitschka
相关产品推荐
相关产品推荐

