Maven test classloader doesn't include target/classes folder - java

I have a Maven project built on JDK 11, making full use of the Java Module System.
My problem is that I'd like to use Spring Data JPA repositories in my tests, but the Jpa Bean factory can't find my repository interfaces.
After some digging, I found that the reason is the classloader that Spring is trying to use to find the repository interfaces doesn't include the folder where the compiled main class files are saved, target/classes. Despite the fact that the Maven surefire documentation says that the directory should be included as the 2nd highest priority.
To better describe the issue, I created a demo GitHub repo, where I demonstrate my problem.
Without the module-info.java everything seems to work fine.

Related

Eclipse m2e doesn't support Maven test-jar with JPMS modules

I have a project 'java11-core' that generates a test jar artifact to share with project 'java11-app'. These projects build fine with command line Maven, but within Eclipse, the classes shared in the test jar cannot be found.
Version Info:
Apache Maven 3.6.0 (command line and Eclipse)
Java version: 11.0.1, vendor: Oracle Corporation
Eclipse IDE: Version: 2018-09 (4.9.0)
M2E Plugin: 1.9.1.20180912-1601
I originally created these to projects as tradition non-JPMS projects. These projects compiled and ran tests normally as expected. After I added module-info.java to both java11-core and java11-app, the Eclipse compiler could not recognize the shared test files from the core project.
Here is a snapshot of the package explorer for an overview of the project structure.
The added java11-app and java11-core module-info contents respectively:
module com.java11.app {
exports com.java11.app;
requires com.java11.core;
}
module com.java11.core {
exports com.java11.core;
}
As you can see, I do not export the test utilities package from com.java11.core. I do not want to export the test packages because this would make the test classes publicly available. I also do not wish to introduce a new test project, because in real-world scenarios, this is very likely to require cyclic dependencies between test utilities and the projects they assist in testing.
Build errors for in AppTest.java. The failure reported by Eclipse is interesting is that it does not claim it cannot find the CoreTestUtil class, but rather:
The type com.java11.test.core.util.CoreTestUtil is not accessible AppTest.java /java11-app/src/test/java/com/java11/app line 8 Java Problem
CoreTestUtil cannot be resolved AppTest.java /java11-app/src/test/java/com/java11/app line 21 Java Problem
My assumption is that the lack of an export for this package from java11-core and/or the lack of a requires for this package in java11-app make eclipse believe the access is restricted, even though the classes exist in a separate test-jar.
The module path for java11-app shows it includes java11-core as a module, and the Without test code is set to No.
I know I am working with newly release features and suspect that sharing test classes across Eclipse JPMS project is not yet supported. But, I am not sure where to look (Eclipse? M2E plugin) for an update on it being supported. I am also not aware of a work-around that would allow me to be productive while adopting JPMS for my software projects.
For those that believe test utilities should not be shared this way...
This subject has been characterized as a best-practice issue that should be resolved by refactoring test utilities into a separate module. I respect this perspective, but in attempting to follow that guidance, I found myself being forced to violate other best-practices, including DRY (Don't Repeat Yourself), and cyclic dependencies between packages.
It is common for a test utility to emerge while developing a module that both assists in effective testing of that module, as well as depends on that module. This creates a cycle if those utilities are pulled out to separate module. Furthermore, some of these utilities can be equally useful when testing other modules that depend upon that module. This creates duplicate code if those utilities are copied to a new test module for dependents. This reasoning may have been why Maven 'test-jar' support was originally added.
Eclipse does not support multiple module-info per project: in whatever source folder (main or test), you must only have one module-info.
From Eclipse point of view, your only luck is to create a new Java project referencing the other and with its proper module-info/exports:
module mod.a {
exports com.example.a;
// com.example.a.Main
}
module mod.a.tests { // (1)
exports com.example.a.tests;
// com.example.a.tests.MainUtils calling com.example.a.Main
requires mod.a;
}
In case (1), you will have problems if you don't use mod.a.tests: Java will never find com.example.a.Main, probably because the second project shadows the first project.
I am not an OSGI expert, but I think that's one of those reason for why most Eclipse plugin do have a main and test projects: org.eclipse.m2e.core is patched by org.eclipse.m2e.core.tests
However module-info does not have any knowledge of "patches": you may patch a module on command line (java --patch-module), but not in module-info itself: perhaps Eclipse could do that on your behalf, but it don't.
As you can see, two project in Eclipse = two Maven module.
In the case of Maven, you can certainly create other artefacts with the same build (and I do think it tends to pollute the dependencies, because every time your secondary artefacts will requires a dependency, it would have to go to the common scope).
This can be done using maven-compiler-plugin, maven-shade-plugin and maven-jar-plugin:
I think you should not rely test-jar because you want to emulate the --patch-module of Java by merging the classes and test-classes directories.
You don't want to import this project in Eclipse due to multiple module-info; or you must ensure that its module-info is only visible to Maven (you can use a profile + m2e.version do detect m2e and disable it).
I fully agree with you. Why should I only use the src-main code from some core module when I also could inherit some src-test functionalities?
But how to handle the scope problem? When I use the "test"-scope I loose the relation to the src-main code. When I dont use the test scope I loose the relation to the src-test code.
My core test code does not change very often, so to get the stuff working in Eclipse
I install the test-jar to my local repository and everything works fine.

Driver binary downloader maven plugin - across multiple modules

We've got a multi-module Selenium Maven project. Each module represents one application that is tested. We've also got a 'core' module which contains everything necessary for the whole setup (driver initialization, utils classes, etc.).
The problem is that we set the plugins and the repository map in every module's pom.xml - it's basically a copy-paste of the plugins and the repository map (this is it: Lazery)
My question is: is it possible to set the plugins and the repository map in our core pom.xml so that it would be used by all the other modules that have a dependency on that core module? I'm not very well-versed in Maven, so so far I haven't been able to find a solution to this.
EDIT: I have managed to solve the plugin issue by using pluginManagement. However, the issue of the RepositoryMap.xml is still there. I need to have the map in every project's test resources in order for the driver-binary-downloader to start the driver.
Resolved the resources problem by placing the Repository Map into the core module and giving the plugin the path.
As mentioned in my edit in the question: the plugin issue was resolved with the pluginManagement

Patching a classpath when running Surefire tests

We are developing code in the context of a legacy Java application that heavily uses static members and system properties, expecting files in various locations on the disk. The builds are run in Maven.
We are trying to allow unit testing of our code without having to deploy, configure and start the whole application. I have managed to do this by patching a small number of classes in the framework, providing my own variants of the relevant source files in Maven's test sources in src/test/java.
As a next step I would like to make this patch re-usable by providing a JAR file that can be pulled in as a test dependency on any project that develops a part of the larger application. I would like to deploy this via our normal binary repository.
Surefire offers an option to set <additionalClasspathElements>, but according to the documentation this works only with absolute paths and will add the dependency at the end of the class path.
In theory ordering the project dependencies correctly could work, but I cannot find any documentation on how that order works across multiple scopes. I would need Maven to guarantee that my test dependency is loaded before the runtime ones.
What is a reliable way of patching classes for a Surefire run by using a JAR pulled via Maven's dependency resolution mechanisms?

Indirectly referenced from required .class files

I'm getting below error in STS:
The type org.springframework.core.env.EnvironmentCapable cannot be resolved. It is indirectly referenced from required .class files
This sounds like a transitive dependency issue. What this means is that your code relies on a jar or library to do something - evidently, you depend on Spring framework code. Well, all that Spring code also depends on libraries and jars.
Most likely, you need to add the corerctly versioned org.springframework.core jar to your classpath so that the EnvironmentCapable class can be found when your IDE attempts to build your project.
This might also be a jar collision issue as well, although that sounds less likely. When an application experiences jar collision (also known as "dll hell"), the compiler is finding multiple jars and classes with the same fully-qualified name. For example, let's say you added Spring to your classpath, along with the entire Tomcat server library. Well, those two jars may contain the same exact named classes, maybe the same version, maybe different versions. But either way, when the compiler looks for that EnvironmentCapable class, it finds two (in this contrived example) - one in the Spring jar and one in the Tomcat jar. Well, it doesn't know which one to choose, and so it throws a ClassDefNotFoundException, which would/could manifest itself as the error you experienced.
I faced same error while i work with spring security on spring-security-config.i jsut deleted that jar in maven repo and gave maven->update Project in eclipse.
it is resolved.Please try it once.
From command line, run "mvn clean install", you'll see project failed and you'll see artifacts in the logs that cause such a problem.
After that, remove artifacts from .m2/repository, then maven update from eclipse.
To avoid jar collision, make sure you declare your dependency versions under the properties tag in the aggregate pom.xml, and use the property name as a placeholder throughout the project. For example 4.2.5.RELEASE in the parent pom, and then in the child modules just use ${spring.version} instead of 4.2.5.RELEASE. This way you can avoid having two different versions of the same library on the classpath.
Also it is recommended to be consistent with the version of spring dependencies. Use the same version for spring-core, spring-web etc.
If you are using maven, then you can use the maven enforcer plugin to ensure dependency convergence, and avoid further issues with transitive dependencies.

Applying Maven to a project

I've been asked to apply Maven to a project. After browsing a dozen sites it appears that it's quite vast and I'm not familiar as I'd like with similar tools like Ant. Why is it used/preferred and what does it offer over a standard Eclipse project? Also, how could it be added to an existing project?
Why is it used/preferred and what does
it offer over a standard Eclipse
project?
It is a build tool which can build your project without the need for an IDE like Eclipse. It can create a jar or war or other artifacts from the source, performing a bunch of steps like compilation, running unit tests, etc.
Where maven scores over ant is in managing third-party dependencies and in convention over configuration (which mean less lines of build script if you follow convention).
Also, how could it be added to an
existing project?
You start by creating a new maven project, following the step here.
Place it in the root folder of your project
If your source and resource files do not follow maven folder convention, update maven properties suitably referring to this documentation.
Run mvn package
It will fail if it needs any third party dependencies, which you can add as specified in the doc
With some trial and error, you should have your project running with maven, possibly, much quicker than if you were to set up the same with ant.
Others are already provided sufficient resources to read more about maven.
I suggest to start reading here:
http://www.sonatype.com/books/mvnref-book/reference/public-book.html
Maven is a great tool when you know how to use it. Maven (at core) is a dependency manager.
You include in your pom.xml (similar in function to the build.xml from Ant) all the librairies your project depends on (example : apache commons) along with their version and Maven get them directly from a repository (by default, the central maven repository)
Then you do not have to manually install any jar to make your project work. All is downloaded and cached on your local machine. You can even create an enterprise repository where you put all the jars needed by your company
Maven uses the concept of artifacts which are pre-built library projects with their own dependencies
To mavenize a project, you'll have to write a pom.xml describing your project (examples are numerous), get rid of your libs directory (or whatever classpath you described under Eclipse) and add all your dependencies to your pom.xml
You could also check Mavenizer for a first-start
But Maven is a lot more what i've just said. Read the docs, read poms from librairies and you'll get used to it quickly ;-)
If you use the M2Eclipse plugin from Sonatype, it's just a matter of right clicking the project in the package explorer and choosing Enable Dependency Management in the Maven menu. You are also advised to adjust the directories that contain the sources to the Maven standard directory layout but if you absolutely can't, you can configure that later.
Apart from that: Well, look for tutorials and documentation (for example there is the free book Better builds with Maven. Maven is very complex (yes, I don't think it is simple) and very powerful.

Categories

Resources