{{announcement.body}}
{{announcement.title}}

How to Migrate From JUnit 4 to JUnit 5 Step by Step

DZone 's Guide to

How to Migrate From JUnit 4 to JUnit 5 Step by Step

In this tutorial, we take a look at how you can create and migrate a basic Spring Boot application from JUnit 4 to the refreshed JUnit 5.

· Java Zone ·
Free Resource

Introduction 

The majority of the organizations are facing hurdles in digital transformations due to technical debt. Unit tests are one of them. One of the unit testing frameworks is JUnit. The industry was mostly using version 4 of JUnit to write unit tests. JUnit 5 has been in the market for quite some time, but the migration of JUnit 5 has not picked up steam. This is due to the lack of a clear migration path for developers. 

In this exercise you will learn end-to-end migration from JUnit 4 to JUnit 5. You will first start with a basic Spring Boot project using JUnit 4. The project will be a simple calculator application with dependency classes to see how mocking frameworks like Mockito work along with JUnit. You will write a unit test for this calculator application. You will then introduce JUnit 5 in the JUnit 4 project. You will exclude JUnit 4 dependencies. Finally, you will migrate the JUnit 4  test class to JUnit 5. Please note that JUnit 5 is not backward compatible with JUnit 4, but the JUnit team created the JUnit Vintage Project to support JUnit 4 test cases on top of JUnit 5.


You may also enjoy: Take Unit Testing to the Next Level With JUnit 5


JUnit5 provides many new features that will help taking unit testing to next level in form of parameterized test, dynamic test and assertion aggregation to name a few.

If you just want to check out the project without going through steps you can download it from here.

Setup JUnit 4 Project with Test

Add JUnit Dependency

Open the Eclipse editor and create a simple Maven project name as JUnit 4. Please refer to the below image on how to achieve the same. You need to click on "New" under the "File" menu, select "Maven Project" and select a simple archetype. You can think of archetypes as a template under the Maven ecosystem.

JUnit migration

JUnit migration


Select new project

Select new project


New Maven project

New Maven project


 

Check Version and Spring Boot

Once the project is created, go to the Explorer view in Eclipse and open the pom.xml file of this newly created project. Now add dependencies to POM for spring-boot-starter-web and spring-boot-started-test. You can refer to below snippet for copy and paste as is.

Note that this project is with version 2.0.3 of Spring Boot, with JUnit 4 for unit testing. 

XML
 




x
37


 
1
Note that this project is with version 2.0.3 of spring boot, it was having JUnit 4 for unit testing. Your pom will look like below
2
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4
<modelVersion>4.0.0</modelVersion>
5
<groupId>springbootJUnit4</groupId>
6
<artifactId>unit4</artifactId>
7
<version>0.0.1-SNAPSHOT</version>
8
<packaging>jar</packaging>
9
<name>JUnit-4-applcation</name>
10
<url>http://maven.apache.org</url>
11
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent -->
12
<parent>
13
<groupId>org.springframework.boot</groupId>
14
<artifactId>spring-boot-starter-parent</artifactId>
15
<version>2.0.3.RELEASE</version>
16
</parent>
17
<properties>
18
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
19
</properties>
20
<dependencies>
21
<dependency>
22
<groupId>JUnit</groupId>
23
<artifactId>JUnit</artifactId>
24
<scope>test</scope>
25
</dependency>
26
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
27
<dependency>
28
<groupId>org.springframework.boot</groupId>
29
<artifactId>spring-boot-starter-web</artifactId>
30
</dependency>
31
<dependency>
32
<groupId>org.springframework.boot</groupId>
33
<artifactId>spring-boot-starter-test</artifactId>
34
</dependency>
35
</dependencies>
36
</project>
37
 
          


Parent POM

The Spring Boot project works on the concept of parent POM inheritance. Parent POM contains version dependencies entry for helper projects like JUnit. This snippet will help you understand why 2.0.3 used JUnit 4.

The right side in the image is the latest version of  spring-boot-test. The JUnit 4 dependency is still part of newer spring-boot-test project because of backward compatibility. It essentially means if there are existing JUnit 4 test cases available in the same project, JUnit’s will not break. In the next few steps, we will see how we will exclude JUnit 4 fully and move to JUnit 5 fully.

JUnit 4 test cases

JUnit 4 test cases


Add Functional Code

You need to add a now functional code to the project. Create simple Java classes with names as Calculator, Multiplier, Adder, and Subtractor. Once you add these classes you need to enrich them with functional code. In order to move faster, you can copy and paste the code from the below snippet in corresponding classes or refer to the Github link provided at the top. 

Here you are creating a main class and delegating classes. This is necessary to learn the concept of mocking which is essential in any commercial project because most of the projects are designed with separation of concerns in mind. This exercise uses Spring as an IOC container. The Java classes created are injected as bean and autowired.

Java
 




xxxxxxxxxx
1
17


1
package springbootJUnit4.unit4;
2
 
         
3
import org.springframework.stereotype.Component;
4
 
         
5
@Component
6
public class Adder {
7
    
8
 
         
9
    public Integer add(Integer a, Integer b) {
10
        // TODO Auto-generated method stub
11
        return a + b;
12
    }
13
    
14
}
15
 
         
16
 
         
17
 
         



Java
 




x
15


 
1
package springbootjunit4.unit4;
2
 
          
3
import org.springframework.stereotype.Component;
4
 
          
5
@Component
6
public class Subtractor {
7
 
          
8
    
9
    public Integer subtract(Integer a, Integer b) {
10
        return a - b;
11
    }
12
    
13
    
14
}
15
 
          


Java
 




xxxxxxxxxx
1
14


 
1
package springbootjunit4.unit4;
2
 
          
3
import org.springframework.stereotype.Component;
4
 
          
5
@Component
6
public class Multiplier {
7
    
8
    public Integer multiply(Integer a, Integer b) {
9
        return a*b;
10
    }
11
 
          
12
}
13
 
          
14
 
          


Java
 




xxxxxxxxxx
1
44


 
1
package springbootjunit4.unit4;
2
 
          
3
import org.springframework.beans.factory.annotation.Autowired;
4
import org.springframework.stereotype.Component;
5
 
          
6
@Component
7
public class Calculator {
8
    
9
    @Autowired
10
    Adder adder;
11
    
12
    @Autowired
13
    Subtractor subtractor;
14
    
15
    @Autowired
16
    Multiplier multiplier;
17
    
18
    
19
    public Integer subtract(Integer a, Integer b) {
20
        if( a < b) {
21
            throw new IllegalArgumentException("first argument cannot be less than second");
22
            
23
        }
24
        
25
        return subtractor.subtract(a, b);
26
    }
27
    
28
    public Integer multiplier(Integer a, Integer b) {
29
        if(a == 0 || b == 0) {
30
            throw new IllegalArgumentException("Input cannot be zero");
31
        }
32
        
33
        return multiplier(a, b);
34
    }
35
 
          
36
    public Integer add(Integer a, Integer b) {
37
        if(a <0 || b < 0) {
38
            throw new IllegalArgumentException("Invalid input positive integers only");
39
        }
40
        return adder.add(a, b);
41
    }
42
 
          
43
}
44
 
          


Add Test Code

You need to create test class called CalculatorTest. To keep it simple, you just copy and paste the code snippet in the created test class. In this class you are using  SpringBootTest  annotation  @RunWith(SpringJUnit4ClassRunner.class)  annotation. The  Runwith annotation is JUnit 4 specific annotation. This annotation provides the test run engine. 

Your annotated dependency classes like Adder and Subtractor in class with the MockedBean  annotation. This annotation will create mocked instances of the dependency classes and inject it in the execution path during JUnit run. This is the least invasive way of injecting dependency; otherwise, you either have to create a setter and getter in the Calculator class or create an constructor accepting these dependencies. These two approaches will not add any value, but will increase boiler plate code. This strategy of MockedBean will keep code as close to production as possible and still support unit tests.

In this snippet,  SpringJUnit4ClassRunner  is used to run the test. This will allow us to autowire Spring dependencies in the test class and instantiate the application context for this during test execution. There are many runners available for JUnit one of them is  SpringJUnit4ClassRunner .

Run the test case in Eclipse with run as the JUnit test option by right-clicking on class. It will run with  SpringJUnit4ClassRunner.

Java
 




xxxxxxxxxx
1
54


 
1
package springbootjunit4.unit4;
2
 
          
3
import static org.junit.Assert.assertEquals;
4
 
          
5
import org.junit.Test;
6
import org.junit.runner.RunWith;
7
import org.mockito.Mockito;
8
import org.springframework.beans.factory.annotation.Autowired;
9
import org.springframework.boot.test.context.SpringBootTest;
10
import org.springframework.boot.test.mock.mockito.MockBean;
11
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12
 
          
13
 
          
14
@RunWith(SpringJUnit4ClassRunner.class)
15
@SpringBootTest
16
public class CalculatorTest {
17
 
          
18
    @MockBean
19
    Adder adder;
20
    
21
    @MockBean
22
    Subtractor subtractor;
23
    
24
    
25
    @Autowired
26
    Calculator calculator;
27
    
28
    
29
    @Test
30
    public void testAddition() {
31
        Mockito.when(adder.add(Mockito.anyInt(),Mockito.anyInt())).thenReturn(22);
32
        Integer result = calculator.add(Integer.valueOf(10), Integer.valueOf(12));
33
        assertEquals(Integer.valueOf(22),result);
34
    }
35
    
36
    @Test(expected=IllegalArgumentException.class)
37
    public void testAdditionOneNegativeNumber() {
38
        calculator.add(Integer.valueOf(-10), Integer.valueOf(12));
39
    }
40
    
41
    @Test(expected=IllegalArgumentException.class)
42
    public void testAdditionBothNegativeNumber() {
43
        calculator.add(Integer.valueOf(-10), Integer.valueOf(-12));
44
    }
45
    
46
    @Test
47
    public void testSubstraction() {
48
        Mockito.when(subtractor.subtract(Mockito.anyInt(),Mockito.anyInt())).thenReturn(1);
49
        Integer result = calculator.subtract(Integer.valueOf(11), Integer.valueOf(10));
50
        assertEquals(Integer.valueOf(1),result);
51
    }
52
    
53
}
54
 
          



Running tests

Running tests


Migrate JUnit 4 to JUnit 5

Create JUnit 5 Project

In this step you are going to create a new JUnit 5 project. You go to the context menu by right clicking in Project Explorer view of the Unit 4 project. Please refer to the below snippet to gain more clarity and give it new name sb-junit5.

The project explorer view

The project explorer view


Add JUnit 5 Dependency

As discussed above, the JUnit Vintage engine was responsible for running JUnit 4 tests. In this step, you will exclude that dependency. In order to achieve this you open pom.xml of sb-junit5 project and change spring boot version to latest 2.2.3.RELEASE and exclude the JUnit-vintage-engine dependency from spring boot started test dependency to use JUnit 5 and Mockito. In order to do it faster, you can copy the pom from the snippet directly. This image shows the difference view for pom.xml before and after changes.

Before and after view

Before and after view


 Your final pom file will look like this:

XML
 




xxxxxxxxxx
1
67


 
1
<?xml version="1.0" encoding="UTF-8"?>
2
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4
    <modelVersion>4.0.0</modelVersion>
5
    <parent>
6
        <groupId>org.springframework.boot</groupId>
7
        <artifactId>spring-boot-starter-parent</artifactId>
8
        <version>2.2.3.RELEASE</version>
9
        <relativePath/> <!-- lookup parent from repository -->
10
    </parent>
11
    <groupId>com.example</groupId>
12
    <artifactId>demo</artifactId>
13
    <version>1.0-SNAPSHOT</version>
14
    <name>demo</name>
15
    <description>Demo project for Spring Boot</description>
16
 
          
17
    <properties>
18
        <java.version>1.8</java.version>
19
    </properties>
20
 
          
21
    <dependencies>
22
        <dependency>
23
            <groupId>org.springframework.boot</groupId>
24
            <artifactId>spring-boot-starter-web</artifactId>
25
        </dependency>
26
 
          
27
        <dependency>
28
            <groupId>org.springframework.boot</groupId>
29
            <artifactId>spring-boot-starter-test</artifactId>
30
            <scope>test</scope>
31
            <exclusions>
32
                <exclusion>
33
                    <groupId>org.junit.vintage</groupId>
34
                    <artifactId>junit-vintage-engine</artifactId>
35
                </exclusion>
36
            </exclusions>
37
        </dependency>
38
    </dependencies>
39
 
          
40
    <build>
41
        <plugins>
42
            <plugin>
43
                <groupId>org.springframework.boot</groupId>
44
                <artifactId>spring-boot-maven-plugin</artifactId>
45
            </plugin>
46
            <!--plugin>
47
                <groupId>org.apache.maven.plugins</groupId>
48
                <artifactId>maven-deploy-plugin</artifactId>
49
                <version>${maven-deploy-plugin.version}</version>
50
            </plugin-->
51
        </plugins>
52
    </build>
53
 
          
54
    <distributionManagement>
55
    <snapshotRepository>
56
      <id>nexusdeploymentrepo</id>
57
      <url>http://localhost:9081/repository/maven-snapshots/</url>
58
    </snapshotRepository>
59
    <repository>
60
      <id>nexusdeploymentrepo</id>
61
      <url>http://localhost:9081/repository/maven-releases/</url>
62
    </repository>
63
  
64
  </distributionManagement>
65
 
          
66
</project>
67
 
          


JUnit 5 has introduced junit-vintage-engine to support JUnit 4 test cases. You have to exclude this dependency of vintage engine so that only available engine is of JUnit 5.

Modify Test Code

Open CalculatorTest from the copied project and paste the snippet provided below. There are certain things worth noting here. The import statement for JUnit 5 and JUnit 4 is different as the supporting classes have different packages. The JUnit Runner class changed to  SpringExtension. You have to use  ExtendWith  annotation instead of  RunWith as per JUnit 5.

JUnit 5 has gotten rid of  RunWith annotation in this version as it has introduced this concept of  ExtendWith; this concept is applicable for all other areas in JUnit 5 where one can extend existing functionality coming out of the box, thus giving user leverage to build something on top of existing stuff without reinventing the wheel. For example, it provides you a mechanism to combine annotations from the package to give new meaning, like you want to run certain tests for sanity vs. full suite. You can create a annotation to filter those test cases combining existing  @Test annotaion like  @SanityTest. There is a change in test method having exception in annotation to assertion like below, it is using new method and lambda expression. We added new annotation  DisplayName from JUnit 5 to be more descriptive about methods what they are doing like below.

There is no change to Mockito API though the new version of Mockito has been added by parent pom of spring boot thus supporting Junit5 with Mockito out of the box. Try running the test class as JUnit test.

We added a new annotation  DisplayName  from JUnit 5 to be more descriptive about methods what they are doing like below

 @DisplayName (" Test addition failure for one of the arguments are negative")

Java
 




xxxxxxxxxx
1
63


1
package springbootJUnit4.unit4;
2
 
          
3
import static org.JUnit.jupiter.api.Assertions.assertEquals;
4
import static org.JUnit.jupiter.api.Assertions.assertThrows;
5
 
          
6
import org.JUnit.jupiter.api.DisplayName;
7
import org.JUnit.jupiter.api.Test;
8
import org.JUnit.jupiter.api.extension.ExtendWith;
9
import org.mockito.Mockito;
10
import org.springframework.beans.factory.annotation.Autowired;
11
import org.springframework.boot.test.context.SpringBootTest;
12
import org.springframework.boot.test.mock.mockito.MockBean;
13
import org.springframework.test.context.JUnit.jupiter.SpringExtension;
14
 
          
15
 
          
16
@ExtendWith(SpringExtension.class)
17
@SpringBootTest
18
public class CalculatorTest {
19
 
          
20
    @MockBean
21
    Adder adder;
22
    
23
    @MockBean
24
    Subtractor subtractor;
25
    
26
    
27
    @Autowired
28
    Calculator calculator;
29
    
30
    
31
    @Test
32
    @DisplayName(" Test positive integer Addition")
33
    public void testAddition() {
34
        Mockito.when(adder.add(Mockito.anyInt(),Mockito.anyInt())).thenReturn(22);
35
        Integer result = calculator.add(Integer.valueOf(10), Integer.valueOf(12));
36
        assertEquals(Integer.valueOf(22),result);
37
    }
38
    
39
    @Test()
40
    @DisplayName(" Test addition failure for one of the arguments are negative")
41
    public void testAdditionOneNegativeNumber() {
42
        assertThrows(IllegalArgumentException.class, 
43
                () -> calculator.add(Integer.valueOf(-10), Integer.valueOf(12)));
44
        
45
    }
46
    
47
    @Test()
48
    @DisplayName(" Test addition failure when both the  arguments are negative")
49
        public void testAdditionBothNegativeNumber() {
50
        assertThrows(IllegalArgumentException.class, 
51
                () -> calculator.add(Integer.valueOf(-10), Integer.valueOf(-12)));
52
    }
53
    
54
    @Test
55
    @DisplayName(" Test positive case for substraction")
56
        public void testSubstraction() {
57
        Mockito.when(subtractor.subtract(Mockito.anyInt(),Mockito.anyInt())).thenReturn(1);
58
        Integer result = calculator.subtract(Integer.valueOf(11), Integer.valueOf(10));
59
        assertEquals(Integer.valueOf(1),result);
60
    }
61
    
62
}
63
 
          



There is no change to the Mockito API, though new version of Mockito has been added by parent pom of spring boot thus supporting Junit5 with Mockito out of the box without you doing much. Try running the test class as JUnit test. With this you have your first set of tests migrated successfully to Junit5 using Mockito.

Conclusion

In this exercise, you have learned how to move to a new JUnit 5 version from a JUnit 4 project.  You learned how backward compatibility is ensured with JUnit 4 in JUnit 5. You learned about what packages needs to be imported with new JUnit 5. You learned about a new way of testing exceptions and usage of display name in JUnit 5. You got a brief about what to exclude from Pom in order to completely shut off JUnit 4. There may be cases in your project where you are not inheriting from parent pom of spring boot in those cases these dependencies need to be managed manually. In the next exercise we will learn the usage of advance features of JUnit 5.

Further Reading

7 Reasons to Consider JUnit 5

JUnit 5 - Basics


Topics:
java 8 lambda expressions ,junit 4 ,junit 5 ,junit5 ,maven ,migration ,performance ,unit testing

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}