Kurru
Kurru

Reputation: 14331

Mockito can not mock Random in Java 17

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.

Upvotes: 9

Views: 30129

Answers (4)

user18845580
user18845580

Reputation: 51

upgrade mokito to 4.4.0 it worked

Upvotes: 5

Laksitha Ranasingha
Laksitha Ranasingha

Reputation: 4517

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

As this is a runtime issue, you can add --add-opens as a JVM arg/CLI option to make this type accessible.

As per the Oracle guide here, --add-opens does the following.

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

If you want to 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, a specified package is available in the 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.

Upvotes: 10

Kurru
Kurru

Reputation: 14331

This particular issue was also resolvable using:

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

Upvotes: 11

tgdavies
tgdavies

Reputation: 11474

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

Upvotes: 0

Related Questions