Java 19下如何Mock/Spy java.util.concurrent.ThreadLocalRandom?
问题说明
在Java 19环境中,尝试用Mockito Mock java.util.concurrent.ThreadLocalRandom时触发报错,提示该类是final类无法Mock;但Java 17及以下版本无此问题。即使添加了VM参数--add-opens=java.base/java.util.concurrent=ALL-UNNAMED,问题仍然存在。
测试代码:
final ThreadLocalRandom random = Mockito.mock(ThreadLocalRandom.class); Mockito.when(random.nextInt(Mockito.anyInt())).thenReturn(0);
报错信息:
[ERROR] Failures:
[ERROR] Mockito
Cannot mock/spy class java.util.concurrent.ThreadLocalRandom
Mockito cannot mock/spy following:
final classes
anonymous classes
primitive types
可行解决方案
1. 启用Mockito-inline扩展
Mockito-inline专门支持Mock final类、静态方法等,是解决这类问题的首选方案:
- 确保测试依赖中包含mockito-inline:
<!-- Maven依赖示例,版本需与你的Mockito核心版本匹配 --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency>
- 补充VM参数(Java 19需要额外开放部分模块权限):
--add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED
如果使用的Mockito版本较旧,可能还需要添加--enable-preview参数,新版本Mockito已移除对预览API的依赖,可尝试去掉该参数测试。
2. 封装随机数逻辑为自定义服务
通过封装一层接口和实现类,避免直接依赖ThreadLocalRandom,从根本上解决Mock限制问题:
- 定义随机数服务接口:
public interface RandomProvider { int nextInt(int bound); }
- 实现基于ThreadLocalRandom的服务:
public class ThreadLocalRandomProvider implements RandomProvider { @Override public int nextInt(int bound) { return ThreadLocalRandom.current().nextInt(bound); } }
- 业务代码中注入
RandomProvider,测试时Mock这个接口:
RandomProvider mockRandom = Mockito.mock(RandomProvider.class); Mockito.when(mockRandom.nextInt(Mockito.anyInt())).thenReturn(0);
这种方式兼容性强,还能让代码更符合面向接口编程的原则,后续替换随机数实现也更方便。
3. 备选:使用PowerMock(不推荐)
PowerMock可以通过字节码修改绕过final类的Mock限制,但它与Java 19和新版本Mockito的兼容性较差,容易引发依赖冲突,仅作为无其他方案时的备选:
- 添加PowerMock相关依赖,在测试类上标注
@RunWith(PowerMockRunner.class)和@PrepareForTest(ThreadLocalRandom.class),之后即可正常MockThreadLocalRandom。
内容的提问来源于stack exchange,提问作者sKarwasra

