Having a maven project "Test".
This is the pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>Test</groupId>
<artifactId>Test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>LATEST</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
There is a class /src/main/java/test/Test.java like this:
package test;
public class Test {
public final static String SQL = "CALL sysdate()";
}
And a unit-test /src/test/java/test/unittest/UnitTest.java like this:
package test.unittest;
import java.sql.*;
import org.hsqldb.jdbcDriver;
import test.Test;
public class UnitTest {
public void testSQL() throws SQLException {
try (Connection c = jdbcDriver.getConnection("jdbc:hsqldb:mem:test", null)) {
Statement stmt = c.createStatement();
if (stmt.execute(Test.SQL)) {
ResultSet result = stmt.getResultSet();
while (result.next()) {
System.out.println(result.getString(1));
}
}
}
}
}
If I call mvn install (as you can see) the test is executed.
grim@main:~/workspace/Test$ mvn install
[INFO] Scanning for projects...
[WARNING]
[WARNING] Some problems were encountered while building the effective model for Test:Test:jar:0.0.1-SNAPSHOT
[WARNING] 'dependencies.dependency.version' for org.hsqldb:hsqldb:jar is either LATEST or RELEASE (both of them are being deprecated) @ line 16, column 13
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING]
[INFO]
[INFO] -----------------------------< Test:Test >------------------------------
[INFO] Building Test 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.0:resources (default-resources) @ Test ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO]
[INFO] --- compiler:3.10.1:compile (default-compile) @ Test ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- resources:3.3.0:testResources (default-testResources) @ Test ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO]
[INFO] --- compiler:3.10.1:testCompile (default-testCompile) @ Test ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- surefire:3.0.0-M8:test (default-test) @ Test ---
[INFO] Using auto detected provider org.apache.maven.surefire.junit.JUnit3Provider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running test.unittest.UnitTest
2024-02-27 10:31:31
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.119 s - in test.unittest.UnitTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- jar:3.3.0:jar (default-jar) @ Test ---
[INFO]
[INFO] --- install:3.1.0:install (default-install) @ Test ---
[INFO] Installing /home/grim/workspace/Test/pom.xml to /home/grim/.m2/repository/Test/Test/0.0.1-SNAPSHOT/Test-0.0.1-SNAPSHOT.pom
[INFO] Installing /home/grim/workspace/Test/target/Test-0.0.1-SNAPSHOT.jar to /home/grim/.m2/repository/Test/Test/0.0.1-SNAPSHOT/Test-0.0.1-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.706 s
[INFO] Finished at: 2024-02-27T10:31:31+01:00
[INFO] ------------------------------------------------------------------------
grim@main:~/workspace/Test$
That is the setup!
Problem:
In order to use JPMS I need to mention java.sql in the production module /src/main/java/module-info.java like this:
module Test {
exports test;
requires java.sql; // <-- unittest-dependencies in production-jar
}
Question
How to avoid test-dependencies in production-jar?
You could try and separate the module definitions for your main code and your test code when using the Java Platform Module System (JPMS).
That would allow you to declare
requires java.sql;only for your test module, keeping your production JAR free of test dependencies.The
/src/main/java/module-info.javashould not requirejava.sqlbecause it is not needed for the main application's modules.The
/src/test/java/module-info.javawould be separate module descriptor for your test code. It requiresjava.sqlbecause your tests use JDBC.Make sure your
pom.xmlis set up to compile the testmodule-info.javacorrectly. Maven needs to know that the test code forms a separate module. That typically involves configuring the Maven Compiler Plugin to handle the module path for both the main and test compilation phases. You might need to adjust plugin configurations to make sure your test module-info is recognized and compiled correctly.For instance:
The
<release>tag is used instead of<source>and<target>for better handling of JPMS features.The test compilation phase is specifically configured to recognize the test module. The
--patch-moduleoption is used to specify the testmodule-info.java, allowing it to augment or replace the module declaration for tests.The test module path is adjusted to make sure the test
module-info.javais compiled and recognized as part of the test module.That setup should allow you to maintain separate module descriptors for your production and test code, avoiding the inclusion of test dependencies in your production artifacts.
Make sure the Maven Surefire Plugin, which runs your tests, is configured to support JPMS. That may involve specifying additional module path entries or adjusting the plugin's configuration to recognize your test module setup.
As noted by the OP Grim in the comments,
<useModulePath>true</useModulePath>, which tells Surefire to use the module path instead of the classpath (essential for running tests in a modular environment) istrueby default.The
--add-modules,--add-reads, and--add-exportsarguments adjust the module graph for the test run, allowing the test module to access required modules and packages.The
--patch-moduleargument is used to include the test classes and resources in the module being tested. That is necessary because the test classes are not part of the original module definition.Although, as seen in "How can I add a module to the module path in using the Maven Surefire plugin?", the Maven Surefire Plugin version 3.0.0-M6 and newer automatically adds dependencies with a test scope to the module path. That feature simplifies the inclusion of modules like
simplethingproviderat test time without the need for explicitrequiresstatements in themodule-info.javaof the main module (fancythingproviderin the context of the question).Make sure the Maven Surefire Plugin is at least version 3.0.0-M6:
If you wanted to include a hypothetical module, let's call it
testsupportmodule(another Maven project that provides additional functionality or mocks specifically for testing purposes), only at test time for theTestproject, you could update yourpom.xmlfor theTestproject with:This
pom.xmlsnippet includes thetestsupportmoduleas a test-scoped dependency. This means it will be available on the classpath during the test compilation and execution phases but won't be included in the production jar file or required by the production module descriptor (module-info.java). That would avoid test dependencies in the production artifact while still enabling comprehensive testing using additional modules that provide mock implementations, helpers, or other test-specific functionality.From Naman's comments and "Testing In The Modular World":
The
--patch-moduleoption is used to add or replace files in a module, which is useful when your test code needs to reside in the same package as the classes under test but in a separate source set (e.g.,src/test/java). This approach is particularly beneficial when testing package-private classes or methods.However, if your tests only need to access public classes and methods and are located in a different package, the need for
--patch-modulediminishes. The choice to use--patch-moduledepends on the specific access requirements of your test code to the classes under test.Given that
useModulePathistrueby default in recent versions of the Maven Surefire Plugin, Namam's suggestion to set<useModulePath>tofalseas a workaround would force Maven to use the classpath instead of the module path, essentially bypassing the module system for test execution. This approach can simplify testing scenarios at the cost of not fully leveraging the encapsulation benefits of JPMS during testing.