Search code examples
javamockingjunit5openjdk-11

Production code + Test module-info = Unpossible?


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.java 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.java, 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...


Solution

  • 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!