2522. Dynamic Tests with JUnit 5 in Eclipse
JUnit 5 and Maven


Introduce how to create and run JUnit 5 tests in Eclipse.

1. Overview

1.1 JUnit 5

JUnit is one of the most popular unit-testing frameworks in the Java ecosystem. Although the current stable version is JUnit 4.12, a 5.1.0 version contains a number of exciting innovations, with the goal to support new features in Java 8 and above, as well as enabling many different styles of testing. JUnit is an open source project hosted at Github. JUnit 5 requires Java 8. However, we can still test code that has been compiled with previous versions of the JDK.

1.2 JUnit 5 Architecture

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage.

  • JUnit Platform - To be able to launch junit tests, IDEs, build tools or plugins need to include and extend platform APIs. It defines the TestEngine API for developing new testing frameworks that runs on the platform. It also provides a Console Launcher to launch the platform from the command line and build plugins for Gradle and Maven.
  • JUnit Jupiter - It includes new programming and extension models for writing tests. It has all new junit annotations and TestEngine implementation to run tests written with these annotations.
  • JUnit Vintage - It primary purpose is to support running JUnit 3 and JUnit 4 written tests on the JUnit 5 platform. It’s there are backward compatibility.

1.3 JUnit 5 Dynamic Tests

In JUnit 5, dynamic test cases are represented by DynamicTest class. Here are some essential points:

  • Dynamic tests can be generated by a factory method annotated with @TestFactory. It’s a new annotation of JUnit5.
  • @TestFactory method must return a Stream, Collection, Iterable, or Iterator of DynamicTest instances.
  • @TestFactory methods must not be private or static, and may optionally declare parameters to be resolved by ParameterResolvers.

2. Setting up Maven Project

2.1 Eclipse IDE

Go to https://www.eclipse.org/, download and install or upgrade Eclipse to Oxygen.1a Release (4.7.1a) or later version.

2.2 Maven Project

In Eclipse, create new Maven project. image Specify the location of your new project. Do not select ‘Create a simple project’ option. image Press next, filter for the ‘quickstart’ archetype and select the maven-archetype-quickstart entry. This is the classical Maven example archetype for project creation. image Press next, specify Group Id, Artifact Id, Package, etc, then click Finish. image

2.3 Dependencies in pom.xml

Edit pom.xml, add properties, build and dependencies for jupiter, vintage and platform.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>johnny.tutorial</groupId>
  <artifactId>JUnitDynamicTest</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>JUnitDynamicTest</name>
  <url>https://jojozhuang.github.io</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <junit.jupiter.version>5.1.1</junit.jupiter.version>
    <junit.vintage.version>5.1.1</junit.vintage.version>
    <junit.platform.version>1.1.1</junit.platform.version>
  </properties>

  <build>
     <plugins>
       <plugin>
         <artifactId>maven-compiler-plugin</artifactId>
         <version>3.1</version>
       </plugin>
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <version>2.19</version>
         <dependencies>
           <dependency>
             <groupId>org.junit.platform</groupId>
             <artifactId>junit-platform-surefire-provider</artifactId>
             <version>${junit.platform.version}</version>
           </dependency>
         </dependencies>
       </plugin>
     </plugins>
   </build>

   <dependencies>
     <dependency>
       <groupId>org.junit.platform</groupId>
       <artifactId>junit-platform-runner</artifactId>
       <version>${junit.platform.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-engine</artifactId>
       <version>${junit.jupiter.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.junit.vintage</groupId>
       <artifactId>junit-vintage-engine</artifactId>
       <version>${junit.vintage.version}</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
</project>

2.4 Java Files

Create a class named ‘Solution’ in the ‘johnny.tutorial.JUnitDynamicTest’ package with the following content. This is the solution for the first algorithm question in LeetCode.

package johnny.tutorial.JUnitDynamicTest;
import java.util.HashMap;

/*
 Two Sum

 Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

Example:

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

 */
public class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] res = new int[]{0,0};
        if (nums == null || nums.length < 2) {
            return res;
        }

        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();

        for (int i = 0; i < nums.length; i++) {
            if(map.containsKey(nums[i])) {
                res[0] = map.get(nums[i]);
                res[1] = i;
                return res;
            } else {
                map.put(target - nums[i], i);
            }
        }
        return res;
    }
}

Create JUnit class based on JUnit 4 to test the ‘towSum’ method. We define three test methods here.

  • testInvalidInput()
  • testSmallInput()
  • testLargeInput()
package johnny.tutorial.JUnitDynamicTest;
import static org.junit.Assert.*;

import org.junit.Test;

public class SolutionTest {

    Solution solution = new Solution();

    @Test
    public void testInvalidInput() {
        System.out.println("testInvalidInput");

        assertArrayEquals(new int[2], solution.twoSum(null, 0));
        assertArrayEquals(new int[2], solution.twoSum(new int[] {}, 0));
    }

    @Test
    public void testSmallInput() {
        System.out.println("testSmallInput");

        assertArrayEquals(new int[] {1, 2}, solution.twoSum(new int[] { 1, 0, -1 }, -1));
        assertArrayEquals(new int[] {8, 9}, solution.twoSum(new int[]{1,2,3,4,5,6,7,8,9,10}, 19));
    }

    @Test
    public void testLargeInput() {
        System.out.println("testLargeInput");

        int[] numbers3 = {230,863,916,585,981,404,316,785,88,12,70,435,384,778,887,755,740,337,86,92,325,422,815,650,920,125,277,336,221,847,168,23,677,61,400,136,874,363,394,199,863,997,794,587,124,321,212,957,764,173,314,422,927,783,930,282,306,506,44,926,691,568,68,730,933,737,531,180,414,751,28,546,60,371,493,370,527,387,43,541,13,457,328,227,652,365,430,803,59,858,538,427,583,368,375,173,809,896,370,789};
        assertArrayEquals(new int[]{28, 45}, solution.twoSum(numbers3, 542));
    }
}

2.5 Dynamic Test

Now, let’s create dynamic test with JUnit Jupiter based on JUnit5. Create test file SolutionDynamicTest.java. We define one test method here. It will load the test cases from file ‘testcase.txt’ and generate test methods dynamically.

  • testTwoSum()
package johnny.tutorial.JUnitDynamicTest;

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

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.function.Executable;

public class SolutionDynamicTest {

    private Solution solution;

    @BeforeEach
    public void setUp() {
        solution = new Solution();
    }

    @TestFactory
    public Collection<DynamicTest> testTwoSum() {

        Collection<DynamicTest> dynamicTests = new ArrayList<>();

        try {
            BufferedReader br = new BufferedReader(new FileReader("testcase.txt"));
            try {
                String line;
                while ((line = br.readLine()) != null) {
                    int[] nums = ParserUtil.stringToIntegerArray(line);
                    line = br.readLine();
                    int target = Integer.parseInt(line);
                    line = br.readLine();
                    int[] expected = ParserUtil.stringToIntegerArray(line);
                    // create an test execution
                    int[] ret = solution.twoSum(nums, target);
                    Executable exec = () -> assertArrayEquals(expected, ret);

                    // create a test display name
                    String testCase = "Test Two Sum: Input: " + Arrays.toString(nums) + ", " + target + "; Your answer:" + Arrays.toString(ret) + "; Expected answer: " + Arrays.toString(expected);
                    // create dynamic test
                    System.out.println(testCase);
                    DynamicTest dTest = DynamicTest.dynamicTest(testCase, exec);

                    // add the dynamic test to collection
                    dynamicTests.add(dTest);
                }
            }
            catch (Exception io) {
            	System.out.println(io.getMessage());
            }
            finally {
                br.close();
            }
        } catch (IOException ioe) {
            System.out.println(ioe.getMessage());
        } finally {
        }

        return dynamicTests;
    }
}

Create utility class ParserUtil. It helps to convert string to int array and vice versa.

package johnny.tutorial.JUnitDynamicTest;

public class ParserUtil {
    public static int[] stringToIntegerArray(String input) {
    	// null
        if (input.equals("null")) {
            return null;
        }
        // empty array
        if (input.equals("[]")) {
            return new int[]{};
        }
        input = input.trim();
        input = input.substring(1, input.length() - 1);
        if (input.length() == 0) {
            return new int[0];
        }

        String[] parts = input.split(",");
        int[] output = new int[parts.length];
        for(int index = 0; index < parts.length; index++) {
            String part = parts[index].trim();
            output[index] = Integer.parseInt(part);
        }
        return output;
    }

    public static String integerArrayToString(int[] nums, int length) {
        if (length == 0) {
            return "[]";
        }

        String result = "";
        for(int index = 0; index < length; index++) {
            int number = nums[index];
            result += Integer.toString(number) + ", ";
        }
        return "[" + result.substring(0, result.length() - 2) + "]";
    }

    public static String integerArrayToString(int[] nums) {
        return integerArrayToString(nums, nums.length);
    }
}

Create text file named testcase.txt with the following content. Each 3 lines represent as one test case. The first line is the nums array, the second line is the target, and the third line is the expected array needs to be returned after calling Solution.twoSum() method. There are totally 5 test cases defined in this file.

null
0
[0,0]
[]
0
[0,0]
[1,0,-1]
-1
[1,2]
[1,2,3,4,5,6,7,8,9,10]
19
[8, 9]
[230,863,916,585,981,404,316,785,88,12,70,435,384,778,887,755,740,337,86,92,325,422,815,650,920,125,277,336,221,847,168,23,677,61,400,136,874,363,394,199,863,997,794,587,124,321,212,957,764,173,314,422,927,783,930,282,306,506,44,926,691,568,68,730,933,737,531,180,414,751,28,546,60,371,493,370,527,387,43,541,13,457,328,227,652,365,430,803,59,858,538,427,583,368,375,173,809,896,370,789]
542
[28, 45]

3. Testing

3.1 Running JUnit4 Test

In Eclipse, select the SolutionTest class, right-click on it and select Run-as -> JUnit Test. You should see all the three methods created based on JUnit4 passed the testing. image

3.2 Running JUnit5 Test

In Eclipse, select the SolutionDynamicTest class, right-click on it and select Run-as -> JUnit Test. You should see 5 test methods passed the testing. image

4. Source Files

5. Reference