How do I open packages and require dependencies on test scope modules only for JUnit testing
Asked Answered
R

2

20

I'm migrating a jar project from java 10 using classpath to java 11 using the java 9 jigsaw modules. There are JUnit5 tests for the project. The test dependencies are provided at test scope by maven. How to make all packages open for testing but not open when the module is used by another project?

The jar project is just providing a few classes (like a utility project) for other projects (so no main class needed).

The project got 5 packages at /src/main/java/a/b/c/. 2 of them should be accessible for projects using this jar. The other 3 are for internal use only (used by the accessible ones). The tests are located at /src/test/java/a/b/c/. These tests have dependencies (JUnit, mockito, junt-params) provided in test scope, since the tests are not relevant for the projects using this jar

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>

I have provided a module-info.java at /src/main/java/ :

module moduleName {

requires java.base;

exports a.b.c.package1;
exports a.b.c.package3;
}

so now the public classes in package 1 and 3 should be accessible to other projects as expected (I have not been able to verify this yet).

Running the Tests now results in java.lang.reflect.InaccessibleObjectException: Unable to make a.b.c.package1.collections.SomeTest() accessible: module moduleName does not "opens a.b.c.package1" to unnamed module @6a84a97d

when I open the packages (with opens) everything runs smooth. But now all packages are now opened. I expect package 2, 4 and 5 to be only accessible while testing and 1 and 3 should not be open for reflection (so only exported).

I came to think, since maven tells me unnamed module @6a84a97d, this might be a module created for testing. That made me try adding a module-info.java at /src/test/java/ for testing.

module moduleNameTest {
requires moduleName; // the code to test
requires java.base;  // java.base could be transient on moduleName

// test dependencies
requires org.junit.jupiter.api;
requires org.junit.jupiter.params;
}

now maven (3.5.4) states:

src/test/java/module-info.java:[3,20] module not found: moduleName
src/test/java/module-info.java:[4,31] module not found: org.junit.jupiter.api
src/test/java/module-info.java:[5,31] module not found: org.junit.jupiter.params

Technologies:

  • java openJDK 11
  • mvn 3.5.4
  • JUnit 5.3.0
  • Surefire Plugin 3.0.0-M3
  • mockito 2.21.0

As stated earlier I expect package 2, 4 and 5 to be only accessible while testing and the tests to be run on building the jar with maven. Package 1 and 3 should be exported for use in other projects but not open for reflection (so only exported not opened).

If you need additional information don't hesitate to ask.

Thanks in Advance

Kevin

Rutilant answered 7/1, 2019 at 5:48 Comment(0)
T
9

"Welcome to Testing In The Modular World", Kevin.

I compiled a blog about that topic here: https://github.com/sormuras/testing-in-the-modular-world

Basically, when it comes to white-box testing, you need to tweak the module system either at test compile or test runtime to allow testing frameworks to by-pass the module system barriers.

I guess, you're on the right track ... maybe Surefire does the wrong thing? Want to give https://github.com/sormuras/junit-platform-maven-plugin I wrote a shot? This plugin supports black- and white-box testing out of the box. Especially, this plugin shines, when you provide a test/java/module-info.java test module descriptor.

See this "picture" for how to organize modular tests without touching the main module descriptor:

src ├── main │ └── java │ ├── foo │ │ ├── PackageFoo.java │ │ └── PublicFoo.java │ └── module-info.java <------------------ module foo { exports foo; } ├── test │ └── java .--- open module foo { │ ├── foo / exports foo; │ │ └── PackageFooTests.java / requires org.junit.jupiter.api; │ └── module-info.[java|test] <----< } └── it \ └── bar °---- --add-reads └── src foo=org.junit.jupiter.api └── test --add-opens └── java foo/foo=org.junit.platform.commons ├── bar │ └── PublicFooTests.java └── module-info.java <------ open module bar { requires foo; requires org.junit.jupiter.api; }

This pattern should be easy to adopt to your setup as well.

Related question: How do you organize tests in a modular Java project?

Tusker answered 7/1, 2019 at 8:2 Comment(8)
Hi @Somuras, Thanks for your reply. Seems I have to put --add-opens and --add-reads before every package rather then saying --add-opens package1 package2 package3 ... after figuring this out it worked. By the way, is there a possibility to highlight the test results in mvn output (as the were in java 8) where the 223 in [INFO] [ 223 tests successful ] is highlighted in green and failed tests are highlighted red?Rutilant
You're welcome. Please open an issue regarding the coloring here: github.com/sormuras/junit-platform-maven-plugin Thanks in advance.Tusker
"every package" -- see mail.openjdk.java.net/pipermail/jigsaw-dev/2017-January/… for details.Tusker
I am having the exact same problem as described in the question. However, the answer does not help me. If I put module-info.java also into src/test/java I get errors that the file is duplicated as it already exists in src/main/java. If I add module-info.test instead, nothing happens. The file is entirely ignored. Even if I put absolute nonese and syntax errors in there, it has no effect at all. So is the suggested solution outdated?Bundesrat
Thanks, @Sormuras, for bringing this issue to the Jigsaw expert group. As it stands the powers that be seem to decline any participation in standardization for this issue. Will we still be able to achieve an informal agreement among tool providers to use the same style across the board? If you have any hints feel free to join us in bugs.eclipse.org/559601Uglify
@Jörg I added the junit-platform-maven-plugin by sormuras to the build in the pom file and put the module-info.test into test/resources (as done in github.com/sormuras/testing-in-the-modular-world/blob/master/…) this file contains information, jvm parameters, on which modules to require (add-reads) and open (add-opens) Hope this helpsRutilant
Have the same issue as topickstarter. When I add test/java/module-info.java IDEA shows me an error "'module-info.java' already exists in the module". What am I doing wrong?Favorite
@Favorite you need to put the test module info into the test maven resources folder src/test/resources/module-info.test.Rutilant
S
1

The issue could have been solved if junit was packaged as java9 module. Than your error would change to something like

"Unable to make a.b.c.package1.collections.SomeTest() accessible: module moduleName does not "opens a.b.c.package1" to junit5 module @6a84a97d

In this case you could open it just to junit5 module (or any"named" test module which would be trying to use reflection on your classes). Since currently I don't think this is the case (junit5 belongs to unnamed module from java perspective), your alternate options would be

  1. move tests to separate maven module. You would have two maven modules yourModule-1.0 and yourModule-tests-1.0. yourModule-tests-1.0 would depend on yourModule-1.0, so in maven it would build. Then you could open only your test module for reflection with open module moduleNameTest {} in your module-info.java. You main module would still be protected.

  2. prefix package with tests with some "prefix-package", so instead a.b.c.package1.collections.SomeTest() you would have a.b.c.mytests.package1.collections.SomeTest(). Then when you open only your tests package a.b.c.mytests.package1.collections.SomeTest() for reflection while keeping your main source safe, as it remains in another package

  3. you can live with that for now and simply open your package with opens a.b.c.package1.collections.SomeTest(). Unless you work on some shared library, noone ever would probably want to use reflection on your code.

  4. Following on assumption from point 3. the easiest is to open the whole module for reflection, by declaring it as open module moduleName {} instead of just 'module moduleName {}. This way you don't need to adjust your module-info.java each time you create test in not-yet-open package.

Soelch answered 18/1, 2021 at 11:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.