Using JUnit 5 In Pre-Java 8 Projects

This post demonstrates how JUnit 5 can be used in pre-Java 8 projects and explains why it could be a good idea. JUnit 5 requires at least Java 8 as runtime environment, so you want to update your whole project to Java 8. But sometimes there exists reason why you can't immediately update your project to Java 8. For example, the version of your application server in production only supports Java 7. But an update isn't be taken quickly because of some issues in your production code. Now, the question is how can you use JUnit 5 without update your production code to Java 8? You can set up the Java version separately for production code and for test code.

 1<!-- in Maven -->
 2<build>
 3  <plugins>
 4    <plugin>
 5      <artifactId>maven-compiler-plugin</artifactId>
 6      <configuration>
 7        <source>7</source>
 8        <target>7</target>
 9        <testSource>8</testSource>
10        <testTarget>8</testTarget>
11      </configuration>
12    </plugin>
13  </plugins>
14</build>
1// in Gradle
2sourceCompatibility = '7'
3targetCompatibility = '7'
4
5compileTestJava {
6  sourceCompatibility ='8'
7  targetCompatibility = '8'
8}

Precondition is that you use a Java 8 JDK for your build. If you try to use Java 8 feature in your Java 7 production code, Maven and Gradle will fail the build.

1// Maven
2[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.0:compile (default-compile) on project junit5-in-pre-java8-projects: Compilation failure
3[ERROR] /home/sparsick/dev/workspace/junit5-example/junit5-in-pre-java8-projects/src/main/java/Java7Class.java:[8,58] lambda expressions are not supported in -source 7
4[ERROR]   (use -source 8 or higher to enable lambda expressions)
 1// Gradle
 2> Task :compileJava FAILED
 3/home/sparsick/dev/workspace/junit5-example/junit5-in-pre-java8-projects/src/main/java/Java7Class.java:8: error: lambda expressions are not supported in -source 7
 4Function<String, String > java8Feature = (input) -> input;
 5^
 6(use -source 8 or higher to enable lambda expressions)
 71 error
 8
 9FAILURE: Build failed with an exception.
10
11* What went wrong:
12Execution failed for task ':compileJava'.
13> Compilation failed; see the compiler error output for details.

Now you can introduce JUnit 5 in your project and start writing test with JUnit 5.

 1<!-- Maven-->
 2<dependency>
 3  <groupId>org.junit.jupiter</groupId>
 4  <artifactId>junit-jupiter-api</artifactId>
 5  <scope>test</scope>
 6</dependency>
 7<dependency>
 8  <groupId>org.junit.jupiter</groupId>
 9  <artifactId>junit-jupiter-engine</artifactId>
10  <scope>test</scope>
11</dependency>
12<dependency>
13  <groupId>org.junit.jupiter</groupId>
14  <artifactId>junit-jupiter-params</artifactId>
15  <scope>test</scope>
16</dependency>
17<!-- junit-vintage-engine is needed for running elder JUnit4 test with JUnit5-->
18<dependency>
19  <groupId>org.junit.vintage</groupId>
20  <artifactId>junit-vintage-engine</artifactId>
21  <scope>test</scope>
22</dependency>
1// in Gradle
2dependencies {
3  testCompile 'org.junit.jupiter:junit-jupiter-api:5.3.2'
4  testCompile 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
5  testCompile 'org.junit.jupiter:junit-jupiter-params:5.3.2'
6  testCompile 'org.junit.vintage:junit-vintage-engine:5.3.2'
7}

Your old JUnit 4 tests need not be migrated, because JUnit 5 has a test engine, that can run JUnit 4 tests with JUnit 5. So use JUnit 5 for new tests and only migrate JUnit 4 tests if you have to touch them anyway.

Although you can't update your production code to a newer Java version, it has some benefit to update your test code to a newer one.

The biggest benefit is that you can start learning new language feature during your daily work when you write tests. You don't make the beginner's mistake in the production code. You have access to new tools that can help improve your tests. For example, in JUnit 5 it's more comfortable to write parameterized tests than in JUnit 4. In my experience, developer writes rather parameterized test with JUnit 5 than with JUnit 4 in a situation where parameterized test make sense.

The above described technique also works for other Java version. For example, your production code is on Java 11 and you want to use Java 12 feature in your test code. Another use case for this technique could be learning another JVM language like Groovy, Kotlin or Clojure in your daily work. Then use the new language in your test code.

For Maven projects, this approach has one little pitfall. IntelliJ IDEA ignores the Java version configuration for test code. It uses the configured Java version in production code section for the whole project. An issue is already opened. A workaround is also described in this issue. So only the Maven build gives you the feedback if your production code uses correct Java version. IntelliJ hasn't this problem for Gradle projects. Here, it uses the Java version just like it is configured in Gradle build file.

The situation in Netbeans looks better for Maven projects. Netbeans loads the Java configuration for Maven project, correctly. For Gradle projects, I couldn't check it, because in Netbeans 10, there doesn't yet exist a Gradle plugin (status: January 2019, but for Netbeans 9, so maybe something will come)