Home > front end >  Mockito can not mock Random in Java 17
Mockito can not mock Random in Java 17

Time:02-05

Trying to update my project from Java 11 to Java 17 and got an unexpected error from Mockito in a specific test.

mock(java.util.Random.class);

Throws

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

Not sure why Mockito is failing on this test.

CodePudding user response:

The issue here is mockito (via ByteBuddy) is trying to use an unavailable type at runtime (via reflection). In Java 9 onwards, not all modules are available unless you explicitly export/open them.

As this is a runtime issue, you can add --add-options as a JVM arg/CLI option to allow deep reflections.

As mentioned in Oracle guide here, --add-options does the following.

If you have to allow code on the class path to do deep reflection to access nonpublic members, then use the --add-opens runtime option.

If you want export internal types available in compile time as well, you can use --add-exports.

To solve your specific issue; use the following.

--add-opens java.base/jdk.internal.util.random=ALL-UNNAMED.

ALL-UNNAMED means, specified package is available in entire codebase.

However, mocking types that don't belong to you is not a good practice. Maybe, you can simplify this if there's an alternative.

CodePudding user response:

Needing to mock Random indicates that your code hasn't been written to be testable. Do something like this:

   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());
    }

CodePudding user response:

This particular issue was also resolvable using:

mock(SecureRandom.class, withSettings().withoutAnnotations())
  •  Tags:  
  • Related