Mockito无法在Java 17中模拟Random。

7

尝试将我的项目从Java 11更新到Java 17,但在一个特定的测试中,Mockito出现了意外错误。

mock(java.util.Random.class);

抛出异常

Feb 04, 2022 3:07:01 PM com.google.inject.internal.MessageProcessor visit
INFO: An exception was caught and reported. Message: java.lang.IllegalAccessException: class 
    net.bytebuddy.description.annotation.AnnotationDescription$ForLoadedAnnotation cannot access interface
    jdk.internal.util.random.RandomSupport$RandomGeneratorProperties (in module java.base) 
    because module java.base does not export jdk.internal.util.random to unnamed module @2f54a33d
org.mockito.exceptions.base.MockitoException: 
Mockito cannot mock this class: class java.util.Random.

Mockito can only mock non-private & non-final classes.
If you're not sure why you're getting this error, please report to the mailing list.


Java               : 17
JVM vendor name    : Oracle Corporation
JVM vendor version : 17.0.2+8-86
JVM name           : OpenJDK 64-Bit Server VM
JVM version        : 17.0.2+8-86
JVM info           : mixed mode, sharing
OS name            : Mac OS X
OS version         : 12.1

不确定为什么Mockito在这个测试中失败了。


1
请向邮件列表报告。我认为你可能会在那里得到更好的帮助。 - Jim Garrison
1
你到底想要模拟什么?如果你需要一个精确的数字,那就重写测试……不要传递一个 Random 实例。 - OneCricketeer
1
也许你可以查看这个对话 - Oleksandr Pyrohov
4个回答

9

这个问题也可以使用以下方法解决:

mock(SecureRandom.class, withSettings().withoutAnnotations())

8
问题在于 mockito(通过 ByteBuddy)在运行时(通过反射)尝试使用一个不可访问的类型。从 Java 9 开始,除非你显式地导出/开放它们,否则并非所有模块都是可访问的。
由于这是一个运行时问题,您可以添加 --add-opens 作为 JVM arg/CLI 选项,使该类型可访问。
根据 Oracle 指南(这里)--add-opens 的作用如下。

如果你必须允许类路径上的代码进行深层次的反射以访问非公共成员,则使用 --add-opens 运行时选项。

如果要在编译时和运行时导出内部类型,则可以使用 --add-exports
解决您特定的问题,请使用以下内容。 --add-opens java.base/jdk.internal.util.random=ALL-UNNAMEDALL-UNNAMED 表示指定的包在整个代码库中都可用。
然而,模拟不属于您的类型并不是一个好的实践。也许,如果有替代方案,您可以简化这个问题。

9
mock(Random.class, withSettings().withoutAnnotations())这行代码对我也起作用。 - Kurru
我看到了,那看起来代码简单且内联。 - Laksitha Ranasingha
如果代码在模块路径上,你需要使用模块名称而不是 ALL-UNNAMEDALL-UNNAMED 是“类路径上的代码” - jewelsea

4

将Mockito升级至4.4.0,它能够正常工作。


0

需要模拟随机数表明您的代码尚未编写为可测试。请执行以下操作:

   interface RandomSource {
        /**
         * Return a random number that matches the criteria you need
         */
        int nextNumber();
    }

    @Bean
    class DefaultRandomSource implements RandomSource {
        private final Random r = new Random();
        public int nextNumber() {
            return r.nextInt(2);
        }
    }
    
    @Bean
    class ClassToTest {
        private final RandomSource randomSource;

        @Autowired
        public ClassToTest(RandomSource randomSource) {
            this.randomSource = randomSource;
        }

        public String doSomething() {
            return randomSource.nextNumber() == 0 ? "Foo" : "Bar";
        }
    }
    
    @Test
    void testDoSomething() {
        RandomSource r = mock(RandomSource.class);
        when(r.nextNumber()).thenReturn(0);
        ClassToTest classToTest = new ClassToTest(r);
        assertEquals("Foo", classToTest.doSomething());
    }

1
我不相信嘲弄代理对象在本质上比嘲弄原始核心对象更好。 - Kurru
有比嘲笑 Random 更糟糕的事情,没错。 - tgdavies
3
显然,没有必要嘲笑接口,因为你可以直接按照需要实现接口即可。 - Holger
10
值得注意的另一点是,JDK 17中已经存在这样一个接口:RandomGenerator。当你将RandomGenerator作为输入时,你不需要再实现一个关于Random的包装器,因为它已经实现了该接口。并且,你可以通过仅实现一个方法nextLong()来进行“mock”。例如:RandomGenerator g = () -> 0L; g.longs(5).forEach(System.out::println); return g.nextBoolean()? "Foo": "Bar"; - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接