Raino Kolk

@raino.kolk

Mixing JUnit4 and JUnit5

Photo by Yomex Owo on Unsplash

JUnit is one of the most popular testing frameworks for the Java programming language. It has also been important in the development of Test Driven Development, or TDD. In 2018, the latest version of this framework was released, JUnit5, and many programmers who use the JUnit platform have had to decide whether or not to refactor their existing code that is written in a previous version of JUnit.

<tl;dr> This article is about mixing JUnit4 and JUnit5 into your ongoing project to get JUnit5 benefits without refactoring all already written JUnit4 tests. The intention is not to describe JUnit5 in depth but give a guideline to migrate JUnit5 support.

JUnit5 has many benefits that developers will want to use. Better exception handling, better parameterized tests, multiple testrunners and so on. If you are like me, and want to migrate into a newer platform as soon as possible, you probably also know what a pain it is to do so.

Fortunately, JUnit5 has very good support to work side by side with version 4. I think this is important in projects where there are hundreds of already written tests. To refactor all of them is enormous work and it is much easier to give up than go on with adopting something new.

In the following post, I’ve broken down the steps you will need to follow in order to introduce JUtin5 support for tests, while still supporting JUnit4 tests.

Lets assume we have gradle project

plugins {
id 'java'
id 'com.adarshr.test-logger' version '1.6.0'
}

group 'rainoko'
version '1.0-SNAPSHOT'

sourceCompatibility = 11

repositories {
mavenCentral()
}

dependencies {
testCompile('junit:junit:4.12')
}

wrapper {
gradleVersion = '5.2.1'
}

with some simple class SimpleNaturalCalculator.java

package ee.rainoko.junit5demo;

public class SimpleNaturalCalculator {
public int addition(int arg1, int arg2) {
return arg1 + arg2;
}

public int substraction(int arg1, int arg2) {
return arg1 - arg2;
}

public int multiplication(int arg1, int arg2) {
return arg1 * arg2;
}

public int division(int arg1, int arg2) {
return arg1 / arg2;
}
}

And covered with test SimpleNaturalCalculatorTest.java

package ee.rainoko.junit5demo;

import org.junit.*;

import static org.junit.Assert.*;

public class SimpleNaturalCalculatorJunit4Test {
@Test
public void shouldAdd() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.addition(1, 2);

assertEquals(3, result);
}

@Test
public void shouldSubstract() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.substraction(1, 2);

assertEquals(-1, result);
}

@Test
public void shouldMultiply() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.multiplication(1, 2);

assertEquals(2, result);
}

@Test
public void shouldDivide() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.division(1, 2);

assertEquals(0, result);
}
}

As we see this is a JUnit4 test using annotations from org.junit

Lets run this with the following command:

gradlew test

And we get this result:

ee.rainoko.junit5demo.SimpleNaturalCalculatorTest
  Test shouldAdd PASSED
Test shouldDivide PASSED
Test shouldSubstract PASSED
Test shouldMultiply PASSED

Now we have been setting up a standard project with JUnit4.

Lets proceed. Our goal is to introduce JUnit5 support. Unlike version 4 the 5 has modularized library structure: [https://junit.org/junit5/docs/current/user-guide/]

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

The JUnit Platform serves as a foundation for launching testing frameworks on the JVM. [https://junit.org/junit5/docs/current/user-guide/]

JUnit Jupiter is the combination of the new programming model and extension model for writing tests and extensions in JUnit 5. [https://junit.org/junit5/docs/current/user-guide/]

JUnit Vintage provides a TestEngine for running JUnit 3 and JUnit 4 based tests on the platform.[https://junit.org/junit5/docs/current/user-guide/]

Now knowing that, lets add JUnit5 support. For starters, to prevent confusion, lets rename our junit4 test class to SimpleNaturalCalculatorJunit4Test.java

Lets add JUnit5 dependencies to gradle

// JUnit Jupiter
testImplementation('org.junit.jupiter:junit-jupiter:5.4.0')

// JUnit Vintage
testCompileOnly('junit:junit:4.12')
testRuntimeOnly('org.junit.vintage:junit-vintage-engine:5.4.0') {
because 'allows JUnit 3 and JUnit 4 tests to run'
}

As you see we added junit-jupiter. Also marked junit:4.12 as testCompileOnly. This library is still needed for compilation if we want to have our old junit4 tests without any modification. And then we also added junit-vintage-engine to give junit platform ability to run JUnit4 tests side by side with JUnit5 tests.

Finally we need to add also command to use JUnit5 platform for running tests in gradle

test {
useJUnitPlatform()
}

No lets add JUnit5 test class SimpleNaturalCalculatorJunit5Test.java

package ee.rainoko.junit5demo;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class SimpleNaturalCalculatorJunit5Test {
@Test
void shouldAdd() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.addition(1, 2);

assertEquals(3, result);
}

@Test
void shouldSubstract() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.substraction(1, 2);

assertEquals(-1, result);
}

@Test
void shouldMultiply() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.multiplication(1, 2);

assertEquals(2, result);
}

@Test
void shouldDivide() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.division(1, 2);

assertEquals(0, result);
}
}

As you can confirm from imports we now use junit jupiter. Notice that test class is package-protected and methods also. JUnit5 now allows us to use package-protected modifiers, which, as I think, better exposes the purpose of access modifiers of java.

Lets run our tests

gradlew test

And we have output

ee.rainoko.junit5demo.SimpleNaturalCalculatorJunit4Test
 Test shouldAdd PASSED
Test shouldDivide PASSED
Test shouldSubstract PASSED
Test shouldMultiply PASSED
ee.rainoko.junit5demo.SimpleNaturalCalculatorJunit5Test
  Test shouldAdd() PASSED
Test shouldDivide() PASSED
Test shouldSubstract() PASSED
Test shouldMultiply() PASSED

We can even use mixed test class.

package ee.rainoko.junit5demo;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class SimpleNaturalCalculatorJunitMixedTest {
@org.junit.Test
public void shouldAddJunit4() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.addition(1, 2);

org.junit.Assert.assertEquals(3, result);
org.junit.jupiter.api.Assertions.assertEquals(3, result);
}

@org.junit.jupiter.api.Test
void shouldAddJunit5() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.addition(1, 2);

org.junit.Assert.assertEquals(3, result);
org.junit.jupiter.api.Assertions.assertEquals(3, result);
}

@org.junit.Test
public void shouldSubstractJunit4() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.substraction(1, 2);

assertEquals(-1, result);
}

@org.junit.jupiter.api.Test
void shouldSubstractJunit5() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.substraction(1, 2);

assertEquals(-1, result);
}

@org.junit.Test
public void shouldMultiplyJunit4() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.multiplication(1, 2);

assertEquals(2, result);
}

@org.junit.jupiter.api.Test
void shouldMultiplyJunit5() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.multiplication(1, 2);

assertEquals(2, result);
}

@org.junit.Test
public void shouldDivideJunit4() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.division(1, 2);

assertEquals(0, result);
}

@org.junit.jupiter.api.Test
void shouldDivideJunit5() {
SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

int result = calculator.division(1, 2);

assertEquals(0, result);
}
}

As you can see now we have annotations from org.junit and from org.junit.jupiter.api. The JUnit5 platform chooses right engine for each test method.

You can even mix asserts from old JUnit4 package and new JUnit5 package in either of test type.

gradlew test

And we get output

ee.rainoko.junit5demo.SimpleNaturalCalculatorJunit4Test
  Test shouldAdd PASSED
Test shouldDivide PASSED
Test shouldSubstract PASSED
Test shouldMultiply PASSED
ee.rainoko.junit5demo.SimpleNaturalCalculatorJunitMixedTest
  Test shouldAddJunit4 PASSED
Test shouldDivideJunit4 PASSED
Test shouldMultiplyJunit4 PASSED
Test shouldSubstractJunit4 PASSED
ee.rainoko.junit5demo.SimpleNaturalCalculatorJunit5Test
  Test shouldAdd() PASSED
Test shouldDivide() PASSED
Test shouldSubstract() PASSED
Test shouldMultiply() PASSED
ee.rainoko.junit5demo.SimpleNaturalCalculatorJunitMixedTest
  Test shouldAddJunit5() PASSED
Test shouldDivideJunit5() PASSED
Test shouldMultiplyJunit5() PASSED
Test shouldSubstractJunit5() PASSED
SUCCESS: Executed 16 tests in 1s

Be aware JUnit4 and JUnit5 have different @Before… annotations. If you need to mix them add both annotations to your setUp and tearDown methods.

Hope this article helpt to migrate to JUnit5 even if your project using heavily JUnit4 tests and get benefits now not tomorrow.

Read more

There are plenty of good examples and articles about JUnit5. Read them also

Topics of interest

More Related Stories