8

I have a mock class with a trivial implementation of a service I provide from a module. I'm using OpenJDK 11.03, gradle 5.2.1 and IntelliJ 2019.2.

In /main/code/myPackage/myService.java I have:

package myPackage;
class myService {
   public abstract void someFunction();
}

And in my test/code/somePackage/myMockService I have

package myPackage;
// no import, they're in the same package.
class myMockService extends myService {
   @Override
   public void someFunction() { System.out.prinln("Hello World"); }
}

In my main/code/module-info.java I have

module myModule {
    exports somePackage;
}

I've tried several variations on a test/code/module-info.java, without success. For example:

// "open module" lets anyone use reflection within (mostly JUnit 5 in my case)
import myPackage.myService;
import myPackage.myMockService;
open module myTestModule { 
    exports myPackage;
    provides myService with myMockService
}

The above module-info spews errors about how "module name myTestModule does not match expected name myModule", "package 'myPackage' is not visible" (from myMockModule.java), explaining "package myPackage is declared in module myModule but module myTestModule does not read it"

On the other hand, with the following module-info, I get a different batch of errors (below the code)

import myPackage.myService;
import myPackage.myMockService;
open module myModule {
    provides myService with myMockService;
}

Without a requires myModule;, every reference to the main code branch from my test code gives an "error: cannot find symbol". With a requires myModule;, I get an "error: cyclic dependence involving myModule".

So... my tests can't be in a different module. AND they can't be the same module! [long string of expletives deleted]

  • How do I introduce a mock version of a service in test code rather than creating an entirely different module/gradle sub-project?

  • Or is this simply a case where that's not possible, and while you can have a separate test module-info, you can't do much with it?

  • Or is there some way to dynamically load things at runtime such that I don't have to put every little mock service in any module-info, test or otherwise? Such that ServiceLoader.load will find them. Hmm... perhaps extend ServiceLoader and wrap its usage in main code such that it'll use the right one either in production code or test code...

Mark Storer
  • 15,209
  • 2
  • 37
  • 75
  • I've only ever had a single `module-info.java` file in `src/main/java`. Why is one needed for your test directory if (assuming) it won't be included in your build? – Jacob G. Nov 21 '19 at 16:33
  • It'll be used in the tests. We're entirely modular, and being able to whip up a trivial mock service appears to be none-too-trivial after all... not if you want to get at it via `ServiceLoader.load(...)` like we do in the production code I'm trying to test. – Mark Storer Nov 21 '19 at 16:49
  • A bit off topic, but this question has revealed to me that the java coloring code here doesn't know about: module, open, exports, provides, with, and almost certainly "to". – Mark Storer Nov 21 '19 at 18:01
  • You definitely can have a separate `module-info` for your test code, but I believe that only works with "blackbox testing" due to split packages not being allowed. Can you create a [mre] demonstrating the problem, including the `build.gradle` file? – Slaw Nov 21 '19 at 18:09
  • Well I *mostly* figured it out. See below. – Mark Storer Nov 21 '19 at 21:05

1 Answers1

12

a) Welcome to "Testing in the Modular World"!

TL;DR https://sormuras.github.io/blog/2018-09-11-testing-in-the-modular-world.html

Having one or more dedicated test modules is good. With all bells-and-whistles, read module-info.java declarations. Those test modules are your main modules' first clients. Just make sure, that your build tool packages all main modules before compiling and running the test modules. Otherwise you don't test your main modules as close as possible to reality — others will consume your main modules as JAR files. So should you. This solves all issues with services and multi-release JARs as well.

Now the interesting part: in-module testing, also named white box testing. Or how do test types residing non-exported packages or package-private types in exported packages? Either use a build that knows how to patch test modules into main modules (or vice versa) at test compile and/or test runtime. Like pro or Bach.java (which I maintain), or in your case of using Gradle, see b)elow part of this answer.

b) Gradle and Java main, test, … modules are not friends out-of-the-box, yet

Best plugin-based solution: https://github.com/java9-modularity/gradle-modules-plugin -- which honors the pass theses java command line options at test runtime module-info.test configuration file (which I invented). Here you basically desribe your test module requirements via verbose command line options, although a perfect DSL already exists: module-info-java ... loop back to a) and the module-aware build tools.

c) IntelliJ IDEA and Java test modules are ... improving!

Sergey Brunov
  • 11,755
  • 7
  • 39
  • 71
Sormuras
  • 6,622
  • 29
  • 56
  • I'd already read your blog article on Wednesday, and that's what lead me to believe `.test` would work. Did you already qualify where .test was supported and I just missed it, or is that part missing from the blog article? I think I read that same article elsewhere. I don't recall the *"supported" on some IDEs* part in the version I read. More detail would still be nice. I suggest you also cover the "gray box" option of using "exports myModule to myTestModule", though this still leaves mention of a module that won't exist Out In The World could be a Real Problem depending on your app. – Mark Storer Nov 22 '19 at 15:26