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