JDK 17下Mockito静态Mock失败 Instant.now()返回null如何解决?
问题根因
- JDK 17 默认开启
java.base核心模块强封装,java.time.Clock、java.time.Instant这类内置时间类都在该模块下,默认禁止第三方库通过字节码注入修改类行为。Mockito 实现静态方法Mock靠的是inline mock maker做字节码增强,拿不到对应模块的访问权限时,根本拦截不到Clock的静态方法调用,打桩完全不生效,没被处理的方法默认返回Mockito的默认值null。 - 这个问题和Windows系统没关系,所有JDK 17及以上版本,只要没做模块权限配置,Mock JDK核心类的静态方法都会出同类问题。JDK 11没有强制开启强封装,所以同样的代码跑起来没问题。
解决方案
按改造成本从低到高,三个方案可选:
方案1:加JVM参数开放模块权限(改动最小,不用改代码)
不管是IDE里跑测试,还是用Maven Surefire、Gradle test跑自动化测试,给测试任务加两个JVM启动参数就行,给Mockito开操作java.time包的权限:
--add-opens java.base/java.time=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED
加完原有代码不用动,JDK17下就能正常跑通。
方案2:升级Mockito版本
把Mockito升到4.8.0以上的版本,高版本对JDK17+的适配更好,但要注意:就算是最新版Mockito,Mock JDK核心类的时候还是要配上面的--add-opens参数,这是JDK层面的限制,库本身绕不过去。
方案3:重构代码去掉静态方法依赖(最推荐的长期方案)
业务代码里别直接写Instant.now()这种静态调用,改成依赖注入Clock实例,从根源上不用Mock静态方法,完全不会有JDK版本兼容的问题。
举个改造例子:
原来的业务代码硬调静态方法:
public class TimeService { public Instant getNow() { return Instant.now(); } }
改成注入Clock的写法:
public class TimeService { private final Clock clock; // 生产环境直接注入Clock.systemUTC(),测试的时候传Mock对象就行 public TimeService(Clock clock) { this.clock = clock; } public Instant getNow() { return clock.instant(); } }
测试代码连mockStatic都不用写,逻辑简单还稳定:
@Test void testGetNow() { Instant expected = Instant.parse("2022-03-10T10:15:30Z"); Clock mockClock = mock(Clock.class); when(mockClock.instant()).thenReturn(expected); TimeService service = new TimeService(mockClock); assertEquals(expected, service.getNow()); }
补充:你原来测试里写的
spy(Clock.class)本身没语法问题,权限配置对的情况下是可以正常工作的,不用改。
内容的提问来源于stack exchange,提问作者user871611
相关产品推荐
相关产品推荐

