Mixing JUnit4 and JUnit5

Written by raino.kolk | Published 2019/03/03
Tech Story Tags: java | junit | junit-5 | testing | programming

TLDRvia the TL;DR App

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 {@Testpublic void shouldAdd() {SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

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

_assertEquals_(3, result);  

}

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

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

_assertEquals_(-1, result);  

}

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

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

_assertEquals_(2, result);  

}

@Testpublic 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 PASSEDTest shouldDivide PASSEDTest shouldSubstract PASSEDTest 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 JupitertestImplementation('org.junit.jupiter:junit-jupiter:5.4.0')

// JUnit VintagetestCompileOnly('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 {@Testvoid shouldAdd() {SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

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

_assertEquals_(3, result);  

}

@Testvoid shouldSubstract() {SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

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

_assertEquals_(-1, result);  

}

@Testvoid shouldMultiply() {SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

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

_assertEquals_(2, result);  

}

@Testvoid 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 PASSEDTest shouldDivide PASSEDTest shouldSubstract PASSEDTest shouldMultiply PASSED

ee.rainoko.junit5demo.SimpleNaturalCalculatorJunit5Test

Test shouldAdd() PASSEDTest shouldDivide() PASSEDTest shouldSubstract() PASSEDTest 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.Testpublic 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.Testvoid 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.Testpublic void shouldSubstractJunit4() {SimpleNaturalCalculator calculator = new SimpleNaturalCalculator();

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

_assertEquals_(-1, result);  

}

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

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

_assertEquals_(-1, result);  

}

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

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

_assertEquals_(2, result);  

}

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

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

_assertEquals_(2, result);  

}

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

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

_assertEquals_(0, result);  

}

@org.junit.jupiter.api.Testvoid 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 PASSEDTest shouldDivide PASSEDTest shouldSubstract PASSEDTest shouldMultiply PASSED

ee.rainoko.junit5demo.SimpleNaturalCalculatorJunitMixedTest

Test shouldAddJunit4 PASSEDTest shouldDivideJunit4 PASSEDTest shouldMultiplyJunit4 PASSEDTest shouldSubstractJunit4 PASSED

ee.rainoko.junit5demo.SimpleNaturalCalculatorJunit5Test

Test shouldAdd() PASSEDTest shouldDivide() PASSEDTest shouldSubstract() PASSEDTest shouldMultiply() PASSED

ee.rainoko.junit5demo.SimpleNaturalCalculatorJunitMixedTest

Test shouldAddJunit5() PASSEDTest shouldDivideJunit5() PASSEDTest shouldMultiplyJunit5() PASSEDTest 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


Published by HackerNoon on 2019/03/03