Intenté actualizar mi proyecto de Java 11 a Java 17 y obtuve un error inesperado de Mockito en una prueba específica.
mock(java.util.Random.class);Lanza
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.1No estoy seguro de por qué Mockito está fallando en esta prueba.
El problema aquí es que mockito (a través de ByteBuddy) está tratando de usar un tipo inaccesible en tiempo de ejecución (a través de la reflexión). Desde Java 9 en adelante, no se puede acceder a todos los módulos a menos que los exporte/abra explícitamente.
Como se trata de un problema de tiempo de ejecución, puede agregar --add-opens como una opción JVM arg/CLI para que este tipo sea accesible.
Según la guía de Oracle aquí , --add-opens hace lo siguiente.
Si tiene que permitir que el código en el classpath haga una reflexión profunda para acceder a miembros no públicos, entonces use la opción --add-opens runtime.
Si también desea exportar tipos internos disponibles en tiempo de compilación, puede usar --add-exports .
Para resolver su problema específico; utiliza lo siguiente.
--add-opens java.base/jdk.internal.util.random=ALL-UNNAMED .
ALL-UNNAMED significa que un paquete específico está disponible en todo el código base.
Sin embargo, burlarse de los tipos que no le pertenecen no es una buena práctica. Quizás puedas simplificar esto si hay una alternativa.
La necesidad de simular Random indica que su código no ha sido escrito para ser verificable. Haz algo como esto:
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()); }Este problema en particular también se pudo resolver usando:
mock(SecureRandom.class, withSettings().withoutAnnotations())