Java modules: accessibility problems for Mockito 2.20.0 - java

I am migrating from Java 8 to Java 10, and I am running my test which now fails because of package protected classes. The build is run under maven 3.5.4 + Oracle JDK 10.0.2:
maven-compiler-plugin 3.7.0 + asm 6.2
maven-surefire-plugin 2.22.0 + asm 6.2 + junit 5.2.0
asm 6.2 is required for both compiler/surefire because of a bug in the version of ASM used by those plugins.
mockito-core 2.20.0 (but was using 2.20.0 with Java 8 before).
Eclipse Photon R
The project can be found here ide-bugs.zip (it is located at Eclipse forum because I've made this Topic on Eclipse for another problem, this time with Eclipse having local error with module).
The test is very simple: we try to mock different class, with different access level - all of which were working in Java 8.
package protected class
public class but not exported, not opened
public class not exported but opened to Mockito
public class not exported but opened to all
package protected class not exported but opened to Mockito
package protected class not exported but opened to all
In Java 8, case 1, 5 and 6 are the same (access to package protected). Case 2, 3 and 4 are the same (access to public).
The test fails because Mockito is unable to either:
class org.mockito.codegen.NotExportedOpenToMockitoProtected$MockitoMock$117073031 cannot access its superclass nodatafound.mjpmsuc.withopens.NotExportedOpenToMockitoProtected
class org.mockito.codegen.NotExportedNotOpenedPublic$MockitoMock$365628885 (in unnamed module #0x3f07b12c) cannot access class nodatafound.mjpmsuc.internal.NotExportedNotOpenedPublic (in module nodatafound.mockito_jpms_usecase) because module nodatafound.mockito_jpms_usecase does not export nodatafound.mjpmsuc.internal to unnamed module #0x3f07b12c
Mockito effectively have a Automatic-Module-Name but is seen as the unamed module because all jar found in the class path for a big "unnamed module".
While I'm fine with migrating from package-protected to non exported package, I fail to understand how I can address the problem keeping my interface/class not visible to other modules ?
[edit] updated the version of plugin/dependency one month after, no result.

I found part of answer to my problem here: https://blog.codefx.org/java/java-module-system-tutorial/#Open-Packages-And-Modules
Mockito is using reflection to access classes from module or class path.
Mockito is in the "unnamed module" because Maven adds it into the class path rather than the module path. This explains why the opens package to org.mockito never works: there is no org.mockito module.
Maven Surefire does not care to contribute to the "opens" of the module in order to allow Mockito to access it.
Mockito is (no longer?) able to mock non-private & non-final classes class. By any means package protected class are private. The error is rather explicit: Mockito create a class extending the package protected class, which now fails (it was working before, but this was probably because Mockito created the class in the same package than the one being mocked).
Nevertheless, this give a problematic configuration in the pom.xml of each module:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
--add-opens nodatafound.mockito_jpms_usecase/nodatafound.mjpmsuc=ALL-UNNAMED
</configuration>
</plugin>
We need to explicitly add opens to an unnamed module. This should not be done in the module-info.java because it expose the module to all other modules or jars which is against encapsulation.
This is problematic because:
You need to specify it in the pom.xml for each package.
It add additional burdens to the surefire configuration which I prefer simple.
You don't have validation from the IDE; Eclipse will validate module-info.java marking invalid package.
m2e does not pass to Eclipse JUnit plugin the necessary <argLine /> making the test fail in Eclipse.
The maven approach (which is the same in Eclipse, and perhaps Gradle as far as I know) does not permits an additional module-info for the tests; eg: lets test dependency be modular (this could be probably be done using a dedicated test module per source module like Eclipse does for plugin' tests).

Related

Error when running the Spring Boot testing codes in a JMPS modular application

I am trying to create a modular Spring Boot sample using JMPS which introduced in Java 9.
Currently, I created a standalone Maven module for the testing work.
module greeting.webapp.test {
requires greeting.webapp;
requires spring.test;
requires spring.boot;
requires spring.web;
requires spring.boot.starter.webflux;
requires spring.boot.starter.test;
requires spring.boot.test;
requires spring.boot.test.autoconfigure;
requires org.junit.jupiter;
requires org.junit.jupiter.api;
requires org.junit.jupiter.params;
requires org.junit.jupiter.engine;
requires org.junit.platform.commons;
requires org.assertj.core;
requires mockito.junit.jupiter;
}
And when run the sample test, I got the following errors.
Exception in thread "main" java.lang.IllegalAccessError: class org.junit.platform.launcher.TestIdentifier (in unnamed module #0x6325a3ee) cannot access class org.junit.platform.commons.util.Preconditions (in module org.junit.platform.commons) because module org.junit.platform.commons does not export org.junit.platform.commons.util to unnamed module #0x6325a3ee
at org.junit.platform.launcher.TestIdentifier.from(TestIdentifier.java:56)
at com.intellij.junit5.JUnit5IdeaTestRunner.<clinit>(JUnit5IdeaTestRunner.java:86)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:377)
at com.intellij.rt.junit.JUnitStarter.getAgentClass(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:210)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
And in the test module, I have to move the test scoped deps to compile time to make it work in jmps, how to resolve this issue?
There is no easy way, or no good way at all, imho.
The problem you are having is that you have not configured maven surefire correctly. You can try that - I did and somehow was unlucky, but I did not invest too much time in making it work (neither do I think that will work, but that is a different problem). Instead I configured surefire plugin manually, via:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<argLine>
--add-exports org.junit.platform.commons/org.junit.platform.commons.util=ALL-UNNAMED
--add-exports org.junit.platform.commons/org.junit.platform.commons.logging=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
Overall, I like gradle approach more. You can read on my experience with it here. I also don't think that IDEs (Intellij in my case) have yet proper support to run a single (maven based project) test, unlike gradle. But, to be fair, I only tried that against your repo, so far...
You can also read about a rather neat approach that gradle has taken when you need to declare modules here, with their dedicated plugin.

Java9 Multi-Module Maven Project Test Dependencies

I have a multi-module maven project with three modules core, utils and test-utils
Core has the following dependencies definition
<dependency>
<groupId>my.project</groupId>
<artifactId>utils</artifactId>
</dependency>
<dependency>
<groupId>my.project</groupId>
<artifactId>test-utils</artifactId>
<scope>test</scope>
</dependency>
I have added Java 9 module-info.java definitions for all three modules and core's looks like this:
module my.project.core {
requires my.project.utils;
}
However I cannot figure out how to get core's test classes to be able to see the test-utils classes during test execution. When maven-surefire-plugin attempts the test run I get class not found.
If I add a requires my.project.testutils; to core's module-info.java:
module my.project.core {
requires my.project.utils;
requires my.project.testutils; //test dependency
}
Then at compile time I get an error that the my.project.testutils module can't be found (presumably because it's only brought in as a test dependency).
How does one work with test dependencies in a Java 9 modular world? For obvious reason's I don't want my main code to pull in test dependencies. Am I missing something?
With maven and java9, if your my.project.testutils is a test scope dependency, you don't need to explicitly include(requires) it in the module descriptor.
The test dependencies are taken care via the classpath itself. So you can simply remove the testutils and it would be patched by maven while executing tests.
module my.project.core {
requires my.project.utils;
}
Refer to the slide 30 pertaining to maven-compiler-plugin.
I would also suggest you take a look at Where should I put unit tests when migrating a Java 8 project to Jigsaw and this comment by Robert confirming on the implementation that maven follows.
Edit: Created a sample project drawing an analogy that the main module is same as your core, the dependency on guava is same as your utils and the junit dependency is same as your testutils.

Avoid loading a transitively dependent module

I am compiling my legacy source code using JDK 9.0.1 as follows:
javac --add-modules=java.base,java.xml.ws -cp lib\jsr305.jar;lib\javax.annotation-api-1.2.jar TestJava.java
It gives an error because the annotations defined in jsr305.jar are not visible due to split module issue. The error is as follows:
TestJava.java:3: error: cannot find symbol
import javax.annotation.Nonnull;
^
symbol: class Nonnull
location: package javax.annotation
Here the module java.xml.ws.annotation is getting loaded since it is required for java.xml.ws. So it is ignoring the types in jsr305.jar. I don't want this module to be loaded but refer all its annotation types from javax.annotation-api-1.2.jar. I don't want to do --patch-module either because it would break in future releases.
If I use --limit-module=java.xml.ws.annotation it gives the same error. If I remove java.xml.ws from -add-modules, it compiles successfully but I need to export few APIs from it so can't remove it. Is there any way I can load module java.xml.ws but not java.xml.ws.annotation?
EDIT : I think I have added some confusion by giving an example of split between java.xml.ws.annotaion and jsr305.jar. Though it's my actual problem, I am more interested in knowing - can I avoid loading a transitively dependent module, say loading java.xml.ws without loading java.xml.ws.annotation? As per my understanding in JEP 261 it says,
--limit-modules <module>(,<module>)*
where <module> is a module name. The effect of this option is to limit
the observable modules to those in the transitive closure of the named
modules plus the main module, if any, plus any further modules
specified via the --add-modules option.
So, why isn't --limit-module preventing java.xml.ws.annotation from loading?
I know of no way to prevent resolution of a transitive dependency.
Short-term fix
You should be able to make it work by patching the module with --patch-module java.xml.ws=lib\jsr305.jar:lib\javax.annotation-api-1.2.jar. My opinion: If you just want to get your build working on Java 9, that is a good choice. It's a little dubious but still acceptable if you want to use it in production.
If you're worried about long-term compatibility:
I don't think --patch-module will disappear any time soon - do you have a source for that?
I'm pretty sure java.xml.ws will be removed quite soon - it is already deprecated for removal.
In your place I'd worry about the module more than about patching it.
Long-term solution
So for a long-term solution you should remove your dependency on java.xml.ws. JDK-8189188 has a section on this (with lots of links that I was too lazy to copy):
The Reference Implementations (RIs) of JAX-WS and JAXB are a good starting point because they are complete replacements for the java.xml.ws and java.xml.bind modules in JDK 9. The RIs are available as Maven artifacts: (note that they must be deployed on the classpath)
com.sun.xml.ws : jaxws-ri (JAX-WS, plus SAAJ and Web Services Metadata)
com.sun.xml.bind : jaxb-ri (JAXB)
The tools for JAX-WS and JAXB are also available as Maven artifacts:
wsgen and wsimport: com.sun.xml.ws : jaxws-tools, plus tool scripts
schemagen and xjc: com.sun.xml.bind : jaxb-jxc and com.sun.xml.bind : jaxb-xjc, plus tool scripts
There are also Maven artifacts that contain just the APIs of the Java EE technologies:
javax.xml.ws : jaxws-api (JAX-WS, plus javax.xml.soap : javax.xml.soap-api for SAAJ and javax.xml : webservices-api for Web Services Metadata)
javax.xml.bind : jaxb-api (JAXB)
javax.activation : javax.activation-api (JAF)
javax.annotation : javax.annotation-api (Common Annotations)
Adding either the API JARs or the reference implementations to your class path together with all other javax.annotation-related JARs will work because all class path content ends up in the same module (the unnamed one) and thus split packages are no problem there.

How do I determine which version of a class my object is?

I'm trying to write a plugin into a framework application (Joget). My plugin source looks something like this:
public class MyPlugin extends ExtDefaultPlugin implements ApplicationPlugin, ParticipantPlugin {
...
public void execute(){
...
SecurityContextImpl secContext = (SecurityContextImpl) WorkflowUtil.getHttpServletRequest().getSession().getAttribute("SPRING_SECURITY_CONTEXT");
}
}
When I run the plugin, I get the following exception.
java.lang.ClassCastException: org.springframework.security.context.SecurityContextImpl cannot be cast to org.springframework.security.context.SecurityContextImpl
I'm using Maven. Now since both have the same package name, I'm assuming I'm accidentally using the wrong package version in my plugin JAR (that contains the SecurityContextImpl class) than the one in the framework. But I've double-checked and it looks like I'm including the correct one in my plugin package.
Is there a way to see the classloader or source JAR of my class (e.g. using reflection in some manner)? Any other ideas on how to address this?
This type of java.lang.ClassCastException, where both classes names are equals, occur when the same class, or two class with the same name are loaded by 2 different classloaders.
I don't know Joget, but you are talking about plugin. Frameworks often load plugins in separate classloaders to ensure a proper isolation between them.
Since you say I've double-checked and it looks like I'm including the correct one in my plugin package., you may want to remove spring-security from your package, as it's probably already loaded by the framework classloader.
You're using Maven, so you may simply add <scope>provided</scope> to the spring-security dependency (but not sure, since we don't have your pom.xml)
I've got the same exception when I was running my plugin.
There are two cases in general:
1. The class is a local class.
And that is to say, there is no repository(groupId, artificateId, etc) to be deployed in the pom.xml of your plugin. The solution is, go the target folder and open the xxx-0.0.1-snapshot.jar file, then open META-INF/MANIFEST.MF, add the source file of that class /dependency/file.jar, then add the source jar to the dependency folder
Remarks: It is better to give a version of the your local file and add it as shown in your pom.xml to let it be found as src in your code.
<!-- your source jar need to be renamed as example-1.0.0.jar -->
<dependency>
<groupId>this.should.be.the.prefix.of.your.package</groupID>
<artificateId>file.name<artificateId>
<version>1.0.0</version>
<systemPath>${basedir}/lib/stclient_updated-1.0.0.jar</systemPath>
<scope>system</scope>
</dependency>
2. The class is a remote class with repository
I had this type of problem once because the repository is not correct, see also Maven Repository to find the official repository of the source.
I hope that it could help =)
Cheers

Project organization using Maven and ParameterSupplier

I am wondering what would be some suggestions for project / module organization given the following situation:
I have a project DomainObjects in which I have a class MyObject
In /src/test/java of DomainObjects I have the tests for MyObject
I have a project Client that depends on DomainObjects
I would like to add a ParameterSupplier called MyObjectTestSupplier class to provide test instances of MyObject for use by tests in Client.
It seems to make the most sense to provide MyObjectTestSupplier in the DomainObjects project. Here is my dilema...
if I put the supplier in src/test/java of DomainObjects it will not be available to Client.
I don't want to put it in src/main/java of DomainObjects because that means that JUnit would have to be included as a compile dependancy of DomainObjects and thereby be included in my production code.
if I put the supplier in some project DomainObjectsTest I have three options
put just the supplier is the test project but this means that tests in DomainObjects could not use this supplier.
put all the tests and suppliers for DomainObjects in DomainObjectsTest but that means that DomainObjects will be successfully compiled by maven even if tests fail
copy the supplier in both src/test/java of DomainObjects and src/main/java of DomainObjectsTest.
I thought about trying to make DomainObjectsTest a module of DomainObjects but that only works if the packaging for DomainObjects is pom which does not work here.
Thoughts? Suggestions?
EDIT: As an explanation, MyObject is a simple bean (just getters and setters) and I use the ParameterSuppier pattern for providing populated instances of beans. The supplier provides utility methods to easily create populated instances of the bean for use in testing. I do this so that I don't repeat this population code (or the mocking equivilent) throughout my project(s).
As the official Maven mini guide on this particular topic says you should publish a test artifact of the DomainObjects project into your local Maven repository (or anywhere you'd like or able to) and use the DomainObjects-X.Y-tests artifact as a test-scoped dependency in your Client project.
Publishing a test artifact is done by using the jar:test-jar goal of the Maven JAR plugin.
If you include this artifact as a test-scoped dependency in your Client project then any other project that depends on the Client project won't inherit your DomainObjects project's test artifact, because test-scoped dependencies are not transitive by default as stated by the official guide on Maven's dependency mechanism.
That is a bad design example what you're describing. Your unit test can't depend on external dependencies to be "unit".
What you need to do is to mock all dependencies and test only Client code. Use Mockito or another library of your choice to create mock instances of MyObject in client project according to what you expect this class to do. Test MyObject behavior in its own project - DomainObjects.
In mockito creating a mock is just :
import static org.mockito.Mockito.*;
...
MyObject myMock = mock(MyObject.class);
when(myMock.doWhatYouNeed(params)).thenReturn(whatYouExpect);
Edit:
Another ideas
Publish DomainObjects' tests as artifact of type test-jar as descibed here and use it as test-scoped dependency in Client. But this is quite ugly...
Nice design is :
DomainObjectAPI project with MyObject,
DomainObjectTestSupplier using DomainObjectAPI providing suppliers,
DomainObject using DomainObjectAPI for compile and DomainObjectTestSupplier for testing
Client using DomainObjectAPI, DomainObject for compile and DomainObjectTestSupplier for testing.
It's just an overkill.

Categories

Resources