JUnit5 usage memo

What is JUnit5

Needless to say, the latest major version of Java's testing framework as of 2019.

environment

> gradle --version
------------------------------------------------------------
Gradle 5.6.2
------------------------------------------------------------

Build time:   2019-09-05 16:13:54 UTC
Revision:     55a5e53d855db8fc7b0e494412fc624051a8e781

Kotlin:       1.3.41
Groovy:       2.5.4
Ant:          Apache Ant(TM) version 1.9.14 compiled on March 12 2019
JVM:          11.0.4 (AdoptOpenJDK 11.0.4+11)
OS:           Windows 10 10.0 amd64

Hello World

Implementation

build.gradle


plugins {
    id "java"
}

sourceCompatibility = 11
targetCompatibility = 11
[compileJava, compileTestJava]*.options*.encoding = "UTF-8"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation "org.junit.jupiter:junit-jupiter:5.5.2"
}

--If you want to start using JUnit5 for the time being, specify ʻorg.junit.jupiter: junit-jupiter` as the dependency ( details below ).

file organization


|-build.gradle
`-src/test/java/
  `-sample/junit5/
    |-JUnit5Test.java
    |-JUnit5Tests.java
    |-TestJUnit5.java
    `-Hoge.java

――We have 4 types of test classes

JUnit5Test.java


package sample.junit5;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;

class JUnit5Test {
    @Test
    void fail() {
        Assertions.assertEquals(10, 8);
    }

    static class StaticClass {
        @Test
        void fail() {
            Assertions.assertEquals(10, 8);
        }
    }

    static class StaticTest {
        @Test
        void fail() {
            Assertions.assertEquals(10, 8);
        }
    }

    class InnerTest {
        @Test
        void fail() {
            Assertions.assertEquals(10, 8);
        }
    }
}

--Each class defines only the fail () method that always fails --Two static nested classes, StaticClass and StaticTest, Defining the ʻInnerTestclass as an inner class --The above example isJUnit5Test.java, but the remaining three (JUnit5Tests.java, TestJUnit5.java, Hoge.java`) have the same implementation.

Run in ConsoleLauncher

There is a tool called ** ConsoleLauncher ** for running JUnit5 on the command line. The substance is a jar file in which each module of JUnit5 is consolidated into one. Maven Central Repository To download.

Here, download junit-platform-console-standalone-1.5.2.jar and verify it.

#Compile
> gradle compileTestJava

#Run
> java -jar junit-platform-console-standalone-1.5.2.jar ^
       -cp build\classes\java\test ^
       --scan-classpath build\classes\java\test

...

Failures (8):
  JUnit Jupiter:Hoge$StaticTest:fail()
    MethodSource [className = 'sample.junit5.Hoge$StaticTest', methodName = 'fail', methodParameterTypes = '']
    => org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
       ...
  JUnit Jupiter:TestJUnit5:fail()
    MethodSource [className = 'sample.junit5.TestJUnit5', methodName = 'fail', methodParameterTypes = '']
    => org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
       ...
  JUnit Jupiter:TestJUnit5$StaticTest:fail()
    MethodSource [className = 'sample.junit5.TestJUnit5$StaticTest', methodName = 'fail', methodParameterTypes = '']
    => org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
       ...
  JUnit Jupiter:JUnit5Test:fail()
    MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'fail', methodParameterTypes = '']
    => org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
       ...
  JUnit Jupiter:JUnit5Tests:fail()
    MethodSource [className = 'sample.junit5.JUnit5Tests', methodName = 'fail', methodParameterTypes = '']
    => org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
       ...
  JUnit Jupiter:TestJUnit5$StaticClass:fail()
    MethodSource [className = 'sample.junit5.TestJUnit5$StaticClass', methodName = 'fail', methodParameterTypes = '']
    => org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
       ...
  JUnit Jupiter:JUnit5Test$StaticTest:fail()
    MethodSource [className = 'sample.junit5.JUnit5Test$StaticTest', methodName = 'fail', methodParameterTypes = '']
    => org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
       ...
  JUnit Jupiter:JUnit5Tests$StaticTest:fail()
    MethodSource [className = 'sample.junit5.JUnit5Tests$StaticTest', methodName = 'fail', methodParameterTypes = '']
    => org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
       ...

Test run finished after 91 ms
[        10 containers found      ]
[         0 containers skipped    ]
[        10 containers started    ]
[         0 containers aborted    ]
[        10 containers successful ]
[         0 containers failed     ]
[         8 tests found           ]
[         0 tests skipped         ]
[         8 tests started         ]
[         0 tests aborted         ]
[         0 tests successful      ]
[         8 tests failed          ]

--Add the dependency (other than JUnit5) used in the test to the classpath with the -cp option. --JUnit5 classes are in junit-platform-console-standalone-1.5.2.jar ----scan-classpath to specify where to find the test class you want to run --Test results are output to the console

Test class search criteria

> java -jar junit-platform-console-standalone-1.5.2.jar --help
...
  -n, --include-classname=PATTERN
                             Provide a regular expression to include only classes whose fully
                               qualified names match. To avoid loading classes unnecessarily,
                               the default pattern only includes class names that begin with
                               "Test" or end with "Test" or "Tests". When this option is
                               repeated, all patterns will be combined using OR semantics.
                               Default: [^(Test.*|.+[.$]Test.*|.*Tests?)$]
...

--The test class executed in the above example is as follows - Hoge$StaticTest - JUnit5Test - JUnit5Test$StaticTest - JUnit5Tests - JUnit5Tests$StaticTest - TestJUnit5 - TestJUnit5$StaticClass - TestJUnit5$StaticTest --On the contrary, the test of the next class has not been executed. - Hoge - Hoge$InnerTest - Hoge$StaticClass - JUnit5Test$InnerTest - JUnit5Test$StaticClass - JUnit5Tests$InnerTest - JUnit5Tests$StaticClass - TestJUnit5$InnerTest --Specify the test class search condition with the -n or --include-classname option (regular expression specification) -The default is^(Test.*|.+[.$]Test.*|.*Tests?)$ -Result confirmed by REGEXPER --Therefore, classes that start with Test or end with Test or Tests are targeted (including static nested classes). --Inner classes are not covered ( later @Nested is required) --The test class does not have to be public

Run from Gradle

But I think it's usually done from the build tool you're using. Gradle has native support for running JUnit5 since 4.6, so try running it from Gradle.

build.gradle


...

test {
    useJUnitPlatform() //★ Addition
}

--Gradle also supports other testing frameworks such as JUni4 and TestNG --If you use JUnit5, you need to explicitly declare that you will use JUnit5. --The setting for that is test {useJUnitPlatform ()}

Run the test


> gradle test
...

> Task :test FAILED

sample.junit5.Hoge > fail() FAILED
    org.opentest4j.AssertionFailedError at Hoge.java:10

sample.junit5.JUnit5Test > fail() FAILED
    org.opentest4j.AssertionFailedError at JUnit5Test.java:10

sample.junit5.JUnit5Tests$StaticTest > fail() FAILED
    org.opentest4j.AssertionFailedError at JUnit5Tests.java:23

sample.junit5.JUnit5Tests > fail() FAILED
    org.opentest4j.AssertionFailedError at JUnit5Tests.java:10

sample.junit5.TestJUnit5 > fail() FAILED
    org.opentest4j.AssertionFailedError at TestJUnit5.java:10

sample.junit5.Hoge$StaticClass > fail() FAILED
    org.opentest4j.AssertionFailedError at Hoge.java:16

sample.junit5.Hoge$StaticTest > fail() FAILED
    org.opentest4j.AssertionFailedError at Hoge.java:23

sample.junit5.JUnit5Test$StaticClass > fail() FAILED
    org.opentest4j.AssertionFailedError at JUnit5Test.java:16

sample.junit5.JUnit5Test$StaticTest > fail() FAILED
    org.opentest4j.AssertionFailedError at JUnit5Test.java:23

sample.junit5.JUnit5Tests$StaticClass > fail() FAILED
    org.opentest4j.AssertionFailedError at JUnit5Tests.java:16

sample.junit5.TestJUnit5$StaticClass > fail() FAILED
    org.opentest4j.AssertionFailedError at TestJUnit5.java:16

sample.junit5.TestJUnit5$StaticTest > fail() FAILED
    org.opentest4j.AssertionFailedError at TestJUnit5.java:23

12 tests completed, 12 failed
...

BUILD FAILED in 6s
2 actionable tasks: 1 executed, 1 up-to-date

--The following classes were executed - Hoge * - Hoge$StaticClass * - Hoge$StaticTest - JUnit5Test - JUnit5Test$StaticClass * - JUnit5Test$StaticTest - JUnit5Tests - JUnit5Tests$StaticClass * - JUnit5Tests$StaticTest - TestJUnit5 - TestJUnit5$StaticClass - TestJUnit5$StaticTest --More classes are executed than when executed with ConsoleLauncher (more classes are marked with *) --If you run the test from Gradle, the default is the testClassesDirs property of the Test task (https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle All classes existing in the location specified by .api.tasks.testing.Test:testClassesDirs) are targeted. --As the documentation says, the default value is set to the destination directory of sourceSets.test by the Java plugin. --In other words, all the classes under src / test / java are targeted (whether it starts with Test, etc.) --If you want to narrow down the target, [includes](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing. Test: includes) and [excludes](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test: excludes ) --However, the inner class (ʻInnerTest`) is excluded by default, which is the same as ConsoleLauncher.

Classes of interest when run in Eclipse

The confirmed version is 2019-09 Standard Edition of Pleiades.

To execute it, right-click the src / test / java folder and select" Run "->" JUnit Test ".

junit5.jpg

Hoge and StaticClass were also targeted.

Classes of interest when run in IntelliJ IDEA

The confirmed version is the Community version 2019.2.3.

For IDEA, if you open it as a Gradle project, Gradle's test task will run the test. So, the behavior in that case is the same as when running from Gradle.

You can also specify the execution configuration from [Run / Debug Configurations] without using Gradle. However, in that case, it depends on the specification of "Test kind".

junit5.jpg

architecture

junit5.jpg

JUnit5 consists of three major modules (subprojects).

JUnit Platform

The basis for running the test framework on the JVM. It provides a mechanism to execute a module that implements an interface called TestEngine.

It also provides ConsoleLauncher etc. for launching from the console.

JUnit Jupiter

A module for creating and running JUnit 5 tests. Provides JupiterTestEngine that implements TestEngine for JUnit 5.

You can think of Jupiter as JUnit 5.

JUnit Vintage

A class that implements TestEngine to run JUnit 3 and 4 on the JUnit Platform- [VintageTestEngine](https://junit.org/junit5/docs/current/api/org/junit/vintage/engine/VintageTestEngine. A module that provides html).

I think it was prepared for compatibility during the transition period, so it is not necessary if you are introducing new JUnit 5.

in short

JUnit 5 consists of the following two modules.

--Platform for running the test framework --Specific test framework module (Jupiter, Vintage)

The Platform is needed to run the tests. Jupiter is needed when you want to write tests in JUnit 5, and Vintage is needed when you want to run JUnit 4 on the JUnit Platform.

Required Artif

Modules such as Platform and Jupiter correspond to the Group in Maven's classification.

Within each group, there are several more Artifacts.

** Artifact dependencies within each module **

junit5.jpg

When you actually write your JUnit 5 tests, you need to select just the right ones from these Artifacts and add them to your dependencies. As you can see from the figure above, it is difficult at first glance to determine which one is needed.

So, in 5.4.0, an Artifact called junit-jupiter was added to the ʻorg.junit.jupiter` Group.

** Figure with junit-jupiter added **

junit5.jpg

junit-jupiter is an Artifact that summarizes only the minimum dependencies needed to write tests in JUnit 5. So if you just want to write tests in JUnit 5, you only need to add one of these Artifacts to your dependencies.

How to write a test

Test method

package sample.junit5;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class JUnit5Test {
    @Test
    void success() {
        Assertions.assertEquals(10, 10);
    }

    @Test
    void fail() {
        Assertions.assertEquals(10, 8);
    }
}

-The method annotated with @Test becomes the test method. --The package is different from org.junit.Test up to JUnit4. --It seems to be able to mix both 4 and 5 tests during the transition period to 4-> 5.

Pre-processing / post-processing

package sample.junit5;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class JUnit5Test {

    @BeforeAll
    static void beforeAll() {
        System.out.println("JUnit5Test#beforeAll()");
    }
    
    @BeforeEach
    void beforeEach() {
        System.out.println("  JUnit5Test#beforeEach()");
    }
    
    @Test
    void test1() {
        System.out.println("    JUnit5Test#test1()");
    }
    
    @Test
    void test2() {
        System.out.println("    JUnit5Test#test2()");
    }
    
    @AfterEach
    void afterEach() {
        System.out.println("  JUnit5Test#afterEach()");
    }
    
    @AfterAll
    static void afterAll() {
        System.out.println("JUnit5Test#afterAll()");
    }
}

Execution result


JUnit5Test#beforeAll()
  JUnit5Test#beforeEach()
    JUnit5Test#test1()
  JUnit5Test#afterEach()
  JUnit5Test#beforeEach()
    JUnit5Test#test2()
  JUnit5Test#afterEach()
JUnit5Test#afterAll()

-Methods with @BeforeAll are only once at the very beginning in the test class. To be executed --Method must be static -Methods with @BeforeEach are executed before each test method. -Methods with @AfterAll are only once at the very end of the test class. To be executed --Method must be static Methods with @AfterEach will be executed after each test method

Display name

package sample.junit5;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;

@DisplayName("In class")
class JUnit5Test {
    @Test
    @DisplayName("To succeed")
    void success() {
        Assertions.assertEquals(10, 10);
    }

    @Test
    @DisplayName("In failure")
    void fail() {
        Assertions.assertEquals(10, 8);
    }
}

** When run with ConsoleLauncher **

> java -jar junit-platform-console-standalone-1.5.2.jar ^
       -cp build\classes\java\test ^
       --scan-classpath build\classes\java\test

...

.
+-- JUnit Jupiter [OK]
| '--In class[OK]
|   +--To succeed[OK]
|   '--In failure[X] expected: <10> but was: <8>
'-- JUnit Vintage [OK]

Failures (1):
  JUnit Jupiter:In class:In failure
    MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'fail', methodParameterTypes = '']
    => org.opentest4j.AssertionFailedError: expected: <10> but was: <8>
       ...
       [...]

Test run finished after 87 ms
[         3 containers found      ]
[         0 containers skipped    ]
[         3 containers started    ]
[         0 containers aborted    ]
[         3 containers successful ]
[         0 containers failed     ]
[         2 tests found           ]
[         0 tests skipped         ]
[         2 tests started         ]
[         0 tests aborted         ]
[         1 tests successful      ]
[         1 tests failed          ]

** When running on Gradle **

> gradle test
...
> Task :test FAILED

sample.junit5.JUnit5Test > fail() FAILED
    org.opentest4j.AssertionFailedError at JUnit5Test.java:18

2 tests completed, 1 failed

FAILURE: Build failed with an exception.

...

BUILD FAILED in 6s
2 actionable tasks: 1 executed, 1 up-to-date

** HTML report **

junit5.jpg

junit5.jpg

** XML report **

xml:TEST-sample.junit5.JUnit5Test.xml


<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="sample.junit5.JUnit5Test" tests="2" skipped="0" failures="1" errors="0" timestamp="2019-10-08T13:55:57" hostname="niconico" time="0.022">
  <properties/>
  <testcase name="success()" classname="sample.junit5.JUnit5Test" time="0.016"/>
  <testcase name="fail()" classname="sample.junit5.JUnit5Test" time="0.005">
    <failure message="org.opentest4j.AssertionFailedError: expected: &lt;10&gt; but was: &lt;8&gt;" type="org.opentest4j.AssertionFailedError">org.opentest4j.AssertionFailedError: expected: &lt;10&gt; but was: &lt;8&gt;
...

--By adding @DisplayName to a class or method, you can specify the name of the test as an arbitrary string. ――However, Gradle's report seems to be apt to correspond

Nested test

package sample.junit5;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

class JUnit5Test {
    
    @BeforeEach
    void beforeEach() {
        System.out.println("JUnit5Test.beforeEach()");
    }

    @Test
    void test1() {
        System.out.println("  JUnit5Test.test1()");
    }

    @Test
    void test2() {
        System.out.println("  JUnit5Test.test2()");
    }

    @AfterEach
    void afterEach() {
        System.out.println("JUnit5Test.afterEach()");
    }
    
    @Nested
    class NestedTest {

        @BeforeEach
        void beforeEach() {
            System.out.println("  NestedTest.beforeEach()");
        }

        @Test
        void test1() {
            System.out.println("    NestedTest.test1()");
        }

        @Test
        void test2() {
            System.out.println("    NestedTest.test2()");
        }

        @AfterEach
        void afterEach() {
            System.out.println("  NestedTest.afterEach()");
        }
    }
}

Execution result


JUnit5Test.beforeEach()
  JUnit5Test.test1()
JUnit5Test.afterEach()
JUnit5Test.beforeEach()
  JUnit5Test.test2()
JUnit5Test.afterEach()
JUnit5Test.beforeEach()
  NestedTest.beforeEach()
    NestedTest.test1()
  NestedTest.afterEach()
JUnit5Test.afterEach()
JUnit5Test.beforeEach()
  NestedTest.beforeEach()
    NestedTest.test2()
  NestedTest.afterEach()
JUnit5Test.afterEach()

--Annotate a non-static inner class with @Nested and nest test classes Can be --Because it must be non-static, @BeforeAll and @AfterAll cannot be specified as they are [^ 2] --If you really want to specify it, you need to set PER_CLASS by specifying test instance lifecycle below.

[^ 2]: @BeforeAll, @ AfterAll must be specified as static methods, but non- static inner classes cannot define static methods according to the Java language specification.

Specify prerequisites

package sample.junit5;

import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class JUnit5Test {

    @Test
    void test1() {
        Assumptions.assumeTrue(true);

        System.out.println("test1()");
    }

    @Test
    void test2() {
        Assumptions.assumeTrue(false);

        System.out.println("test2()");
    }

    @Test
    void test3() {
        Assumptions.assumingThat(true, () -> {
            System.out.println("test3() assumption.");
        });

        System.out.println("test3()");
    }

    @Test
    void test4() {
        Assumptions.assumingThat(false, () -> {
            System.out.println("test4() assumption.");
        });

        System.out.println("test4()");
    }
}

Execution result


test1()
test3() assumption.
test3()
test4()

...

.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    +-- test1() [OK]
    +-- test2() [A] Assumption failed: assumption is not true
    +-- test3() [OK]
    '-- test4() [OK]

Test run finished after 84 ms
[         2 containers found      ]
[         0 containers skipped    ]
[         2 containers started    ]
[         0 containers aborted    ]
[         2 containers successful ]
[         0 containers failed     ]
[         4 tests found           ]
[         0 tests skipped         ]
[         4 tests started         ]
[         1 tests aborted         ]
[         3 tests successful      ]
[         0 tests failed          ]

-If you use Assumptions.assumeTrue (boolean), the argument is Subsequent tests are run only when you pass true --If it is false, the rest of the processing in the test method will be interrupted. --Aborted test is neither successful nor failed, but aborted -Assumptions.assumingThat (boolean, Executable), the process passed in the second argument will be executed only when the value of the first argument is true.

Disable test

package sample.junit5;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class JUnit5Test {

    @Test
    void test1() {
        System.out.println("test1()");
    }

    @Test
    @Disabled
    void test2() {
        System.out.println("test2()");
    }
}

Execution result


test1()

-Test methods with @Disabled will no longer be executed --Can be attached to the class (in that case, all test methods in the test class will not be executed)

Conditional test

OS

package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;

class JUnit5Test {

    @Test
    @EnabledOnOs(OS.WINDOWS)
    void test1() {
        System.out.println("enabled on windows");
    }

    @Test
    @EnabledOnOs(OS.MAC)
    void test2() {
        System.out.println("enabled on mac");
    }

    @Test
    @DisabledOnOs(OS.WINDOWS)
    void test3() {
        System.out.println("disabled on windows");
    }

    @Test
    @DisabledOnOs(OS.MAC)
    void test4() {
        System.out.println("disabled on mac");
    }
}

Execution result


enabled on windows
disabled on mac

-With @EnabledOnOs, you can enable the test only on a specific OS. -With @DisabledOnOs you can disable the test only on certain OS --For value, specify the constant defined in OS.

Java version

package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.JRE;

class JUnit5Test {

    @Test
    @EnabledOnJre(JRE.JAVA_11)
    void test1() {
        System.out.println("enabled on java 11");
    }

    @Test
    @EnabledOnJre(JRE.JAVA_12)
    void test2() {
        System.out.println("enabled on java 12");
    }

    @Test
    @DisabledOnJre(JRE.JAVA_11)
    void test3() {
        System.out.println("disabled on java 11");
    }

    @Test
    @DisabledOnJre(JRE.JAVA_12)
    void test4() {
        System.out.println("disabled on java 12");
    }
}

Execution result


enabled on java 11
disabled on java 12

-Add @EnabledOnJre to enable testing only on certain Java versions it can -@DisabledOnJre disables testing only on certain Java versions it can --For value, specify the constant defined in JRE.

System properties

package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;

class JUnit5Test {
    
    @Test
    @EnabledIfSystemProperty(named = "java.vendor", matches = "AdoptOpenJDK")
    void test1() {
        System.out.println("enabled if AdoptOpenJDK");
    }

    @Test
    @EnabledIfSystemProperty(named = "java.vendor", matches = "Oracle.*")
    void test2() {
        System.out.println("enabled if Oracle");
    }

    @Test
    @DisabledIfSystemProperty(named = "java.vendor", matches = "AdoptOpenJDK")
    void test3() {
        System.out.println("disabled if AdoptOpenJDK");
    }

    @Test
    @DisabledIfSystemProperty(named = "java.vendor", matches = "Oracle.*")
    void test4() {
        System.out.println("disabled if Oracle");
    }
}

Execution result


enabled if AdoptOpenJDK
disabled if Oracle

-If @EnabledIfSystemProperty is added, the test will be performed based on the value of the system property. Can be enabled -If @DisabledIfSystemProperty is added, the test will be performed based on the value of the system property. Can be disabled --Specify the name of the system property you want to condition in named --Specify the conditional value in matches with a regular expression (whole match)

Environment variable

package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;

class JUnit5Test {
    
    @Test
    @EnabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*\\\\AdoptOpenJDK\\\\.*")
    void test1() {
        System.out.println("enabled if AdoptOpenJDK");
    }

    @Test
    @EnabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*\\\\OpenJDK\\\\.*")
    void test2() {
        System.out.println("enabled if OpenJDK");
    }

    @Test
    @DisabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*\\\\AdoptOpenJDK\\\\.*")
    void test3() {
        System.out.println("disabled if AdoptOpenJDK");
    }

    @Test
    @DisabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*\\\\OpenJDK\\\\.*")
    void test4() {
        System.out.println("disabled if OpenJDK");
    }
}

Execution result


enabled if AdoptOpenJDK
disabled if OpenJDK

-If @EnabledIfEnvironmentVariable is added, the test will be performed based on the value of the environment variable. Can be enabled -If @DisabledIfEnvironmentVariable is added, the test will be performed based on the value of the environment variable. Can be disabled --Specify the name of the environment variable you want to condition in named --Specify the conditional value in matches with a regular expression (whole match)

Tag filtering

package sample.junit5;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

class JUnit5Test {

    @Test
    @Tag("foo")
    @Tag("fizz")
    void test1() {
        System.out.println("test1@(foo, fizz)");
    }
    
    @Test
    @Tag("bar")
    @Tag("fizz")
    void test2() {
        System.out.println("test2@(bar, fizz)");
    }

    @Test
    @Tag("fizz")
    void test3() {
        System.out.println("test3@(fizz)");
    }

    @Test
    @Tag("buzz")
    void test4() {
        System.out.println("test4@(buzz)");
    }
}

** When executed normally **

Execution result



> java -jar junit-platform-console-standalone-1.5.2.jar ^
       -cp build\classes\java\test ^
       --scan-classpath build\classes\java\test ^
       -e junit-jupiter

...
test1@(foo, fizz)
test2@(bar, fizz)
test3@(fizz)
test4@(buzz)

**-When narrowing down by include-tag **

Execution result


> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "fizz"
...
test1@(foo, fizz)
test2@(bar, fizz)
test3@(fizz)

> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "foo & fizz"
...
test1@(foo, fizz)

> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "foo | bar"
...
test1@(foo, fizz)
test2@(bar, fizz)

> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "!foo & fizz"
...
test2@(bar, fizz)
test3@(fizz)

> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "foo | !fizz"
...
test1@(foo, fizz)
test4@(buzz)

junit5.jpg

-You can tag test cases by adding @Tag to test classes and methods. --The character string specified in the tag name must meet the following conditions: --Not null or empty string --Does not contain whitespace --Does not include control characters --Does not include the following reserved characters - ( - ) - , - & - | - ! --Tags can be filtered according to the conditions specified at runtime --For ConsoleLauncher ----include-tag can only target tags that match the conditions ----exclude-tag can only target tags that do not match the conditions --When executing from Gradle, specify as follows

build.gradle


...
test {
    useJUnitPlatform {
        includeTags "foo | !fizz"
    }
}

-[includeTags](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/junitplatform/JUnitPlatformOptions.html#includeTags-java.lang.String ...-) or [excludeTags ](Https: //docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/junitplatform/JUnitPlatformOptions.html#excludeTags-java.lang.String ...-) --Conditions for narrowing down tags can be described with a dedicated ** tag expression ** --Tag expressions can describe complex conditions using operators - !:NOT - &:AND - |:OR --It is also possible to summarize the conditions with parentheses ()

Execution result


> java -jar junit-platform-console-standalone-1.5.2.jar ... --include-tag "(!foo & fizz) | buzz"
...
test2@(bar, fizz)
test3@(fizz)
test4@(buzz)

Test execution order

package sample.junit5;

import org.junit.jupiter.api.Test;

class JUnit5Test {

    @Test
    void bear() {
        System.out.println("bear");
    }

    @Test
    void ant() {
        System.out.println("ant");
    }

    @Test
    void cat() {
        System.out.println("cat");
    }

    @Test
    void dog() {
        System.out.println("dog");
    }
}

Execution result


ant
cat
dog
bear

By default, the execution order of test methods is deliberately determined by a non-trivial algorithm [^ 1]. This is because it is desirable that unit tests do not depend on the execution order.

[^ 1]: In short, I think it's an order that isn't random but can't be guessed.

However, when it comes to integration and functional testing, the order of execution can be important. [^ 8]

[^ 8]: After registering as a master, move individual functions (probably)

At such times, a mechanism is provided to control the test execution order.

Alphabetical order

package sample.junit5;

import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

@TestMethodOrder(MethodOrderer.Alphanumeric.class)
class JUnit5Test {

    @Test
    void bear() {
        System.out.println("bear");
    }

    @Test
    void ant() {
        System.out.println("ant");
    }

    @Test
    void cat() {
        System.out.println("cat");
    }

    @Test
    void dog() {
        System.out.println("dog");
    }
}

Execution result


ant
bear
cat
dog

--Set annotation @TestMethodOrder in test class --For value, specify the Class object of the class that implements MethodOrderer. To do --MethodOrder provides the ability to control the order in which methods are executed. --There are three standard implementations of MethodOrder. -Alphanumeric has the method name String.compareTo (String) Compare and sort by: //docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/String.html#compareTo (java.lang.String)) --If the method names are the same, compare the ones expressed as strings including the arguments.

Specify with Order annotation

package sample.junit5;

import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class JUnit5Test {

    @Test
    @Order(3)
    void bear() {
        System.out.println("bear");
    }

    @Test
    @Order(4)
    void ant() {
        System.out.println("ant");
    }

    @Test
    @Order(2)
    void cat() {
        System.out.println("cat");
    }

    @Test
    @Order(1)
    void dog() {
        System.out.println("dog");
    }
}

Execution result


dog
cat
bear
ant

-Using OrderAnnotation, [@Order](https://junit. org / junit5 / docs / current / api / org / junit / jupiter / api / Order.html) You can specify the order with annotations. --Methods that do not specify @ Order are assigned ʻInteger.MAX_VALUE` by default.

random

package sample.junit5;

import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

@TestMethodOrder(MethodOrderer.Random.class)
class JUnit5Test {

    @Test
    void bear() {
        System.out.println("bear");
    }

    @Test
    void ant() {
        System.out.println("ant");
    }

    @Test
    void cat() {
        System.out.println("cat");
    }

    @Test
    void dog() {
        System.out.println("dog");
    }
}

Execution result


#1st time
ant
dog
cat
bear

#Second time
bear
ant
cat
dog

-If you specify Random, the method execution order will be random.

Test instance life cycle

Default life cycle

package sample.junit5;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class JUnit5Test {

    @BeforeEach
    void before() {
        System.out.println("before@" + this.hashCode());
    }
    
    @Test
    void test1() {
        System.out.println("test1@" + this.hashCode());
    }

    @Test
    void test2() {
        System.out.println("test2@" + this.hashCode());
    }

    @Test
    void test3() {
        System.out.println("test3@" + this.hashCode());
    }
}

Execution result


before@278240974
test1@278240974
before@370370379
test2@370370379
before@671046933
test3@671046933

--By default, a new test class instance is created each time the test method is executed.

Change life cycle for each test class

package sample.junit5;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

@TestInstance(TestInstance.Lifecycle.PER_CLASS) // ★
class JUnit5Test {
    
    @BeforeEach
    void before() {
        System.out.println("before@" + this.hashCode());
    }
    
    @Test
    void test1() {
        System.out.println("test1@" + this.hashCode());
    }

    @Test
    void test2() {
        System.out.println("test2@" + this.hashCode());
    }

    @Test
    void test3() {
        System.out.println("test3@" + this.hashCode());
    }
}

Execution result


before@1504642150
test1@1504642150
before@1504642150
test2@1504642150
before@1504642150
test3@1504642150

--Annotate the test class with @TestInstance and set value to [PER_CLASS]( Specify https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/TestInstance.Lifecycle.html#PER_CLASS) --Then, a test instance will be created for each test class. --Therefore, all the test methods in the same test class are executed in the same instance. --If the life cycle is set to PER_CLASS, @BeforeAll and @AfterAll can be set in the instance method.

package sample.junit5;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class JUnit5Test {
    
    @BeforeAll
    static void staticBeforeAll() {
        System.out.println("staticBeforeAll()");
    }
    
    @BeforeAll
    void beforeAll() {
        System.out.println("beforeAll()");
    }

    @Test
    void test() {
        System.out.println("test()");
    }
    
    @AfterAll
    void afterAll() {
        System.out.println("afterAll()");
    }
    
    @AfterAll
    static void staticAfterAll() {
        System.out.println("staticAfterAll()");
    }
}

Execution result


beforeAll()
staticBeforeAll()
test()
staticAfterAll()
afterAll()

--It is also possible to leave it attached to the static method --The advantage of this is that you can add @BeforeAll and @AfterAll to test classes nested with @ Nested. --If you do not specify PER_CLASS, @BeforeAll, @AfterAll must make the method static. --However, since the class with @ Nested is an inner class, the static method cannot be defined due to the Java language specification. --Therefore, I couldn't define the process I want to execute first and last only in the @ Nested class.

package sample.junit5;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

class JUnit5Test {
    
    @BeforeAll
    static void beforeAll() {
        System.out.println("JUnit5Test.beforeAll()");
    }
    
    @BeforeEach
    void beforeEach() {
        System.out.println("  JUnit5Test.beforeEach()");
    }

    @Test
    void test1() {
        System.out.println("    JUnit5Test.test1()");
    }

    @Test
    void test2() {
        System.out.println("    JUnit5Test.test2()");
    }

    @AfterEach
    void afterEach() {
        System.out.println("  JUnit5Test.afterEach()");
    }
    
    @AfterAll
    static void afterAll() {
        System.out.println("JUnit5Test.afterAll()");
    }
    
    @Nested
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    class NestedTest {

        @BeforeAll
        void beforeAll() {
            System.out.println(" NestedTest.beforeAll() *");
        }

        @BeforeEach
        void beforeEach() {
            System.out.println("   NestedTest.beforeEach()");
        }

        @Test
        void test1() {
            System.out.println("     NestedTest.test1()");
        }

        @Test
        void test2() {
            System.out.println("     NestedTest.test2()");
        }

        @AfterEach
        void afterEach() {
            System.out.println("   NestedTest.afterEach()");
        }

        @AfterAll
        void afterAll() {
            System.out.println(" NestedTest.afterAll() *");
        }
    }
}

Execution result


JUnit5Test.beforeAll()
  JUnit5Test.beforeEach()
    JUnit5Test.test1()
  JUnit5Test.afterEach()
  JUnit5Test.beforeEach()
    JUnit5Test.test2()
  JUnit5Test.afterEach()
 NestedTest.beforeAll() *
  JUnit5Test.beforeEach()
   NestedTest.beforeEach()
     NestedTest.test1()
   NestedTest.afterEach()
  JUnit5Test.afterEach()
  JUnit5Test.beforeEach()
   NestedTest.beforeEach()
     NestedTest.test2()
   NestedTest.afterEach()
  JUnit5Test.afterEach()
 NestedTest.afterAll() *
JUnit5Test.afterAll()

--It is now possible to define the first and last processing only in @ Nested.

Change the default life cycle

--By default, a test instance is created for each test method --This can be changed to instantiate each test class by default --The method is one of the following --Specify in system properties --Specify in the JUnit Platform configuration file --Specify as -Djunit.jupiter.testinstance.lifecycle.default = per_class when specifying in system properties --When specifying in the JUnit Platform configuration file, first place the junit-platform.properties file in the class path root --And specify junit.jupiter.testinstance.lifecycle.default = per_class in this properties file --Recommended for those who use JUnit Platform configuration files --If you use system properties, you will have to remember to specify them in all execution environments. --If you forget to set system properties and the behavior changes, it may be difficult to clarify the cause of the error.

Repeated test

package sample.junit5;

import org.junit.jupiter.api.RepeatedTest;

class JUnit5Test {

    @RepeatedTest(3)
    void test() {
        System.out.println("test");
    }
}

** Execution result (for ConsoleLauncher) **

Execution result(ConsoleLauncher)


test
test
test

...

.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    '-- test() [OK]
      +-- repetition 1 of 3 [OK]
      +-- repetition 2 of 3 [OK]
      '-- repetition 3 of 3 [OK]

Test run finished after 98 ms
[         3 containers found      ]
[         0 containers skipped    ]
[         3 containers started    ]
[         0 containers aborted    ]
[         3 containers successful ]
[         0 containers failed     ]
[         3 tests found           ]
[         0 tests skipped         ]
[         3 tests started         ]
[         0 tests aborted         ]
[         3 tests successful      ]
[         0 tests failed          ]

** Execution result (for Gradle) **

junit5.jpg

-When you annotate a method with @RepeatedTest, the test will be tested the number of times specified by value. Will be repeated --The display name of each repeated test will be repetition <current number of repetitions> of <total number of repetitions>

Specify the display name

package sample.junit5;

import org.junit.jupiter.api.RepeatedTest;

class JUnit5Test {

    @RepeatedTest(
        name = "displayName={displayName}, currentRepetition={currentRepetition}, totalRepetitions={totalRepetitions}",
        value = 3
    )
    void test() {
        System.out.println("test");
    }
}

** Execution result (for Console Launcher) **

Execution result(ConsoleLauncher)


.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    '-- test() [OK]
      +-- displayName=test(), currentRepetition=1, totalRepetitions=3 [OK]
      +-- displayName=test(), currentRepetition=2, totalRepetitions=3 [OK]
      '-- displayName=test(), currentRepetition=3, totalRepetitions=3 [OK]

** Execution result (for Gradle) **

junit5.jpg

--You can specify the display name of the iterative test with the name attribute. --A dedicated placeholder is prepared for specifying the display name, and repeated information can be reflected in the display name. --displayName: Display name of test method --Default is the name of the test method --If @DisplayName is specified, it will be used. --currentRepetition: Current number of repetitions (starting from 1) --totalRepetitions: Total number of repetitions --A predefined display name pattern is provided in @RepetedTest - LONG_DISPLAY_NAME --The pattern is " {displayName} :: repetition {currentRepetition} of {totalRepetitions} "

Receive repetitive information in test method

package sample.junit5;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;

class JUnit5Test {
    
    @BeforeEach
    void before(RepetitionInfo repetitionInfo) {
        printRepetitionInfo("before", repetitionInfo);
    }

    @RepeatedTest(3)
    void test(RepetitionInfo repetitionInfo) {
        printRepetitionInfo("  test", repetitionInfo);
    }

    @AfterEach
    void after(RepetitionInfo repetitionInfo) {
        printRepetitionInfo("after", repetitionInfo);
    }
    
    private void printRepetitionInfo(String method, RepetitionInfo repetitionInfo) {
        int currentRepetition = repetitionInfo.getCurrentRepetition();
        int totalRepetitions = repetitionInfo.getTotalRepetitions();

        System.out.printf("%s (%d/%d)%n", method, currentRepetition, totalRepetitions);
    }
}

Execution result


before (1/3)
  test (1/3)
after (1/3)
before (2/3)
  test (2/3)
after (2/3)
before (3/3)
  test (3/3)
after (3/3)

--The test method annotated with @RepeatedTest holds the current iteration information [RepetitionInfo](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/RepetitionInfo" .html) Can accept objects as arguments --You can also receive with @BeforeEach, @AfterEach --However, if the normal test methods declared with @Test are mixed here, RepetitionInfo cannot be resolved and a runtime error will occur. --You can get the current number of repetitions with getCurrentRepetition () (1 start) --You can get the total number of repetitions with getTotalRepetitions ()

Parameterization test

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class JUnit5Test {
    
    @ParameterizedTest
    @ValueSource(strings = {"hoge", "fuga", "piyo"})
    void test(String value) {
        System.out.println("value=" + value);
    }
}

** Execution result (for Console Launcher) **

Execution result(ConsoleLauncher)


value=hoge
value=fuga
value=piyo

Thanks for using JUnit! Support its development at https://junit.org/sponsoring

.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    '-- test(String) [OK]
      +-- [1] hoge [OK]
      +-- [2] fuga [OK]
      '-- [3] piyo [OK]

Test run finished after 116 ms
[         3 containers found      ]
[         0 containers skipped    ]
[         3 containers started    ]
[         0 containers aborted    ]
[         3 containers successful ]
[         0 containers failed     ]
[         3 tests found           ]
[         0 tests skipped         ]
[         3 tests started         ]
[         0 tests aborted         ]
[         3 tests successful      ]
[         0 tests failed          ]

** Execution result (for Gradle) **

junit5.jpg

-The method annotated by @ParameterizedTest receives the value used in the test as an argument. Will be executed while --There are multiple ways to declare the value to be passed to the argument, but here [@ValueSource](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/ ValueSource.html) using annotations --@ValueSource can statically declare parameters to pass to test methods with attributes such as strings and ʻints --The source that generates the parameter (@ValueSource` in the example of ↑) is called the parameter ** Source **.

Source Enum

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

class JUnit5Test {
    
    @ParameterizedTest
    @EnumSource(TestEnum.class)
    void test(TestEnum value) {
        System.out.println("value=" + value);
    }
    
    enum TestEnum {
        HOGE, FUGA, PIYO
    }
}

Execution result


value=HOGE
value=FUGA
value=PIYO

-@EnumSource Using annotations, constants defined in ʻenumCan be used as a parameter --If you pass aClass object of ʻenum to value, all the defined constants will be used as parameters.

Use only certain constants

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

class JUnit5Test {
    
    @ParameterizedTest
    @EnumSource(value = TestEnum.class , names = {"HOGE", "PIYO"})
    void test(TestEnum value) {
        System.out.println("value=" + value);
    }
    
    enum TestEnum {
        HOGE, FUGA, PIYO
    }
}

Execution result


value=HOGE
value=PIYO

-You can narrow down the constants used in names

Exclude certain constants

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

class JUnit5Test {
    
    @ParameterizedTest
    @EnumSource(value = TestEnum.class, mode = EnumSource.Mode.EXCLUDE , names = {"HOGE", "PIYO"})
    void test(TestEnum value) {
        System.out.println("value=" + value);
    }
    
    enum TestEnum {
        HOGE, FUGA, PIYO
    }
}

Execution result


value=FUGA

-In mode, which condition is specified by names You can specify whether to apply -If EXCLUDE is specified, specify it with names. Excludes constants

Specify with a regular expression

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

class JUnit5Test {
    
    @ParameterizedTest
    @EnumSource(
        value = TestEnum.class,
        mode = EnumSource.Mode.MATCH_ALL,
        names = {"^F.*", ".*H$"}
    )
    void matchAll(TestEnum value) {
        System.out.println("matchAll() value=" + value);
    }

    @ParameterizedTest
    @EnumSource(
            value = TestEnum.class,
            mode = EnumSource.Mode.MATCH_ANY,
            names = {"^F.*", ".*H$"}
    )
    void matchAny(TestEnum value) {
        System.out.println("matchAny() value=" + value);
    }
    
    enum TestEnum {
        FIRST, SECOND, THIRD, FOURTH, FIFTH, SIXTH
    }
}

Execution result


matchAll() value=FOURTH
matchAll() value=FIFTH
matchAny() value=FIRST
matchAny() value=FOURTH
matchAny() value=FIFTH
matchAny() value=SIXTH

-MATCH_ALL or [MATCH_ANY](https://junit. You can use org / junit5 / docs / current / api / org / junit / jupiter / params / provider / EnumSource.Mode.html # MATCH_ANY) to narrow down the target constants with regular expressions. --MATCH_ALL can be narrowed down to constants that match all the conditions --MATCH_ANY can be narrowed down to only constants that match any of the conditions

Source method

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;

class JUnit5Test {
    
    @ParameterizedTest
    @MethodSource
    void test(String value) {
        System.out.println("value=" + value);
    }

    static List<String> test() {
        return List.of("hoge", "fuga", "piyo");
    }
}

Execution result


value=hoge
value=fuga
value=piyo

-@MethodSource can be used to source a method as a parameter --By default, a static method with the same name as the test method and no arguments is automatically selected as the source. --The source method sets the value to be passed as a parameter to "Stream. Must be returned in "convertible type" --What is a "type that can be converted to Stream "is described in the Javadoc and user guide of MethodSource as follows: --Stream, DoubleStream, LongStream, ʻIntStream, Collection, ʻIterator, ʻIterable`, Array of objects, Array of primitive types ――So, most ** like that ** feels good.

Specify the source method

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;

class JUnit5Test {
    
    @ParameterizedTest
    @MethodSource("sourceMethod")
    void test(String value) {
        System.out.println("value=" + value);
    }

    static List<String> sourceMethod() {
        return List.of("HOGE", "FUGA", "PIYO");
    }
    
    static List<String> test() {
        return List.of("hoge", "fuga", "piyo");
    }
}

Execution result


value=HOGE
value=FUGA
value=PIYO

--The name of the source method can be specified by value of @MethodSource.

Source methods from other classes

SourceClass


package sample.junit5;

import java.util.List;

class SourceClass {
    static List<String> createSource() {
        return List.of("foo", "bar");
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;

class JUnit5Test {
    
    @ParameterizedTest
    @MethodSource("sample.junit5.SourceClass#createSource")
    void test(String value) {
        System.out.println("value=" + value);
    }
}

Execution result


value=foo
value=bar

--In value of @MethodSource, you can use the method of the external class as the source by specifying the fully qualified name of the class #method name`.

Pass multiple formal arguments with one parameter

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;

import static org.junit.jupiter.params.provider.Arguments.*;

class JUnit5Test {
    
    @ParameterizedTest
    @MethodSource
    void test1(String string, int i, boolean bool) {
        System.out.printf("test1() string=%s, i=%d, bool=%s%n", string, i, bool);
    }

    static List<Object[]> test1() {
        return List.of(
            new Object[]{"hoge", 11, false},
            new Object[]{"fuga", 17, true},
            new Object[]{"piyo", 19, true}
        );
    }

    @ParameterizedTest
    @MethodSource
    void test2(String string, int i, boolean bool) {
        System.out.printf("test2() string=%s, i=%d, bool=%s%n", string, i, bool);
    }

    static List<Arguments> test2() {
        return List.of(
            arguments("HOGE", 20, true),
            arguments("FUGA", 23, false),
            arguments("PIYO", 28, true)
        );
    }
}

Execution result


test1() string=hoge, i=11, bool=false
test1() string=fuga, i=17, bool=true
test1() string=piyo, i=19, bool=true
test2() string=HOGE, i=20, bool=true
test2() string=FUGA, i=23, bool=false
test2() string=PIYO, i=28, bool=true

--You can pass values to multiple formal parameters with one parameter. --In that case, the source method must be implemented to return Stream (what can be) of ʻObject []. --In this example, List <Object []>` is returned. --You can also use a dedicated container class called Arguments provided by jupiter. it can -[arguments (Object ...)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/Arguments.html#arguments (java.lang.Object ..) .)) or [of (Object ...)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/Arguments.html#of(java.lang. There is a factory method called Object ...)) that you can use to create an instance.

Source CSV text

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class JUnit5Test {
    
    @ParameterizedTest
    @CsvSource({"foo,bar,1", "'hoge,fuga','',2", "fizz,,3"})
    void test(String s1, String s2, int i) {
        System.out.printf("s1=[%s], s2=[%s], i=[%d]%n", s1, s2, i);
    }
}

Execution result


s1=[foo], s2=[bar], i=[1]
s1=[hoge,fuga], s2=[], i=[2]
s1=[fizz], s2=[null], i=[3]

-Use @CsvSource to source static CSV-formatted text it can --Use single quotes (') for quotes --'' is treated as an empty string and complete spaces are treated as null

Source CSV file

Folder tribute tax


`-src/test/
  |-resources/
  | `-test.csv
  `-java/
    `-sample/junit5/
      `-JUnit5Test.java

test.csv


hoge,1
fuga,2
piyo,3

JUnit5Test


package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;

class JUnit5Test {
    
    @ParameterizedTest
    @CsvFileSource(resources = "/test.csv")
    void test(String string, int i) {
        System.out.printf("string=[%s], i=[%d]%n", string, i);
    }
}

Execution result


string=[hoge], i=[1]
string=[fuga], i=[2]
string=[piyo], i=[3]

-Use @CsvFileSource to specify the CSV file in the classpath as the source it can --Specify the path of the CSV file to use with resources --Encoding, line feed code, etc. can be specified by annotation attributes (see Javadoc).

Create a reusable source class

MyArgumentsProvider


package sample.junit5;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;

import java.util.stream.Stream;

import static org.junit.jupiter.params.provider.Arguments.*;

public class MyArgumentsProvider implements ArgumentsProvider {

    @Override
    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {
        return Stream.of(
            arguments("hoge", 1),
            arguments("fuga", 2),
            arguments("piyo", 3)
        );
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;

class JUnit5Test {
    
    @ParameterizedTest
    @ArgumentsSource(MyArgumentsProvider.class)
    void test(String string, int i) {
        System.out.printf("string=[%s], i=[%d]%n", string, i);
    }
}

Execution result


string=[hoge], i=[1]
string=[fuga], i=[2]
string=[piyo], i=[3]

-If you use @ArgumentsSource, you can use [ArgumentsProvider](https://junit. A class that implements org / junit5 / docs / current / api / org / junit / jupiter / params / provider / ArgumentsProvider.html) can be used as a source. --Specify a Class object of a class that implements ʻArgumentsProvider in value` --Can be used when reusing sources in multiple test classes

Parameter conversion

Dilation

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;

import static org.junit.jupiter.params.provider.Arguments.*;

class JUnit5Test {
    
    @ParameterizedTest
    @MethodSource
    void test(int i, long l, double d) {
        System.out.printf("i=%s, l=%s, d=%s%n", i, l, d);
    }
    
    static List<Arguments> test() {
        return List.of(arguments(10, 20, 30));
    }
}

Execution result


i=10, l=20, d=30.0

--Parameters support ** widening primitive conversions ** --Mechanism for converting to larger size primitive types such as byte-> short and ʻint-> long --Even if the actual parameter is ʻint, the argument of the test method can be received by long or double.

Implicit conversion

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.jupiter.params.provider.MethodSource;

import java.io.File;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.util.List;

import static org.junit.jupiter.params.provider.Arguments.*;

class JUnit5Test {
    
    @ParameterizedTest
    @MethodSource
    void test(
        boolean bool, char c, double d, TestEnum e, File file,
        Class<?> clazz, BigDecimal bd, Charset charset, LocalDateTime dateTime
    ) {
        System.out.printf(
            "bool=%s, c=%s, d=%s, e=%s, file=%s, clazz=%s, bd=%s, charset=%s, dateTime=%s%n",
            bool, c, d, e, file, clazz, bd, charset, dateTime
        );
    }
    
    static List<Arguments> test() {
        return List.of(
            arguments(
                "true", "c", "12.34", "FOO", "path/to/file",
                "java.lang.String", "98.76", "MS932", "2019-10-01T12:34:56"
            )
        );
    }
    
    enum TestEnum {
        FOO, BAR
    }
}

Execution result


bool=true, c=c, d=12.34, e=FOO, file=path\to\file, clazz=class java.lang.String, bd=98.76, charset=windows-31j, dateTime=2019-10-01T12:34:56

--Even if the parameter is a character string, it implicitly converts the type of the formal argument of the test method. --It supports various types, and most of the frequently used types that exist in the standard API are supported.

Types and conversion methods that support implicit type conversion
Converted type Conversion method
boolean/java.lang.Boolean Boolean.valueOf(String)[^4]
char/java.lang.Character String.charAt(0)[^3]
byte/java.lang.Byte Byte.decode(String)
short/java.lang.Short Short.decode(String)
int/java.lang.Integer Integer.decode(String)
long/java.lang.Long Long.decode(String)
float/java.lang.Float Float.valueOf(String)
double/java.lang.Double Double.valueOf(String)
anyenumType (java.lang.EnumSubclass) Enum.valueOf(Class, String)
java.time.Duration Duration.parse(CharSequence)
java.time.Instant Instant.parse(CharSequence)
java.time.LocalDate LocalDate.parse(CharSequence)
java.time.LocalDateTime LocalDateTime.parse(CharSequence)
java.time.LocalTime LocalTime.parse(CharSequence)
java.time.MonthDay MonthDay.parse(CharSequence)
java.time.OffsetDateTime OffsetDateTime.parse(CharSequence)
java.time.OffsetTime OffsetTime.parse(CharSequence)
java.time.Period Period.parse(CharSequence)
java.time.Year Year.parse(CharSequence)
java.time.YearMonth YearMonth.parse(CharSequence)
java.time.ZonedDateTime ZonedDateTime.parse(CharSequence)
java.time.ZoneId ZoneId.of(String)
java.time.ZoneOffset ZoneOffset.of(String)
java.io.File new File(String)
java.nio.charset.Charset Charset.forName(String)
java.nio.file.Path Paths.get(String, String...)
java.net.URI URI.create(String)
java.net.URL new URL(String)
java.math.BigDecimal new BigDecimal(String)
java.math.BigInteger new BigInteger(String)
java.util.Currency Currency.getInstance(String)
java.util.Locale new Locale(String)
java.util.UUID UUID.fromString(String)
java.lang.Class * Details will be described later

[^ 3]: Error if length () of the original String is not 1 [^ 4]: null is an error

Java.lang.Class conversion
package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.Arrays;
import java.util.List;

class JUnit5Test {
    
    @ParameterizedTest
    @MethodSource
    void test(Class<?> clazz) {
        System.out.println(clazz);
    }
    
    static List<String> test() {
        return Arrays.asList(
            "int",
            "int[]",
            "int[][]",
            "[I",
            "[[I",
            "java.lang.String",
            "java.lang.String[]",
            "java.lang.String[][]",
            "[Ljava.lang.String;",
            "[[Ljava.lang.String;",
            "sample.junit5.JUnit5Test",
            "sample.junit5.JUnit5Test$InnerClass"
        );
    }
    
    class InnerClass {}
}

Execution result


int
class [I
class [[I
class [I
class [[I
class java.lang.String
class [Ljava.lang.String;
class [[Ljava.lang.String;
class [Ljava.lang.String;
class [[Ljava.lang.String;
class sample.junit5.JUnit5Test
class sample.junit5.JUnit5Test$InnerClass

--Primitive types can specify the type name as it is (ʻint, long, float, etc ...) --An array of primitive types can be specified with a notation such as ʻint [] , ʻint [] [] --The array can be obtained with [Class.getName ()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Class.html#getName ()) Can be specified in the same format as the string ([[I, [Ljava.lang.String; `, etc ...) --Other reference types are specified in binary name (Internally [ClassLoader.loadClass (String)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/ClassLoader.html#loadClass (java) .lang.String))) is used)

Dynamic test

package sample.junit5;

import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.TestFactory;

import java.util.List;

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

class JUnit5Test {
    
    @TestFactory
    List<DynamicNode> testFactory() {
        return List.of(
            dynamicTest("Hoge", () -> System.out.println("Dynamic Hoge!!")),
            dynamicTest("Fuga", () -> System.out.println("Dynamic Fuga!!"))
        );
    }
}

Execution result


Dynamic Hoge!!
Dynamic Fuga!!

...
.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    '-- testFactory() [OK]
      +-- Hoge [OK]
      '-- Fuga [OK]

-@TestFactory You can dynamically generate test cases by using annotations. --Methods annotated with @TestFactory now return a collection of DynamicNode Implement ――Strictly speaking, a "collection" can be one of the following: - java.util.Collection - java.lang.Iterable - java.util.Iterator - java.util.stream.Stream --Array --If you return with Stream, Jupiter will doclose (), so it is safe to use Stream generated by Files.lines (). --DynamicNode itself is an abstract class, so it's actually a subclass DynamicTest or [DynamicContainer]( Use one of https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/DynamicContainer.html) --In the above example, [dynamicTest (String, Executable)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/DynamicTest.html#dynamicTest (java.lang.String, org) I'm creating an instance of DynamicTest using a factory method called .junit.jupiter.api.function.Executable)) --The first argument is the name of the test --The second argument is the content of the test

Dynamic testing life cycle

package sample.junit5;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;

import java.util.List;

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

class JUnit5Test {
    
    @BeforeEach
    void beforeEach() {
        System.out.println("beforeEach()");
    }
    
    @TestFactory
    List<DynamicNode> testFactory() {
        System.out.println("  testFactory()");
        return List.of(
            dynamicTest("Hoge", () -> System.out.println("    Dynamic Hoge!!")),
            dynamicTest("Fuga", () -> System.out.println("    Dynamic Fuga!!"))
        );
    }
    
    @Test
    void test() {
        System.out.println("  test()");
    }
    
    @AfterEach
    void afterEach() {
        System.out.println("afterEach()");
    }
}

Execution result


beforeEach()
  testFactory()
    Dynamic Hoge!!
    Dynamic Fuga!!
afterEach()
beforeEach()
  test()
afterEach()

--@BeforeEach and @AfterEach are only executed before and after the method in which @TestFactory is set, not before and after each dynamic test.

Nesting dynamic tests

package sample.junit5;

import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.TestFactory;

import java.util.List;

import static org.junit.jupiter.api.DynamicContainer.*;
import static org.junit.jupiter.api.DynamicTest.*;

class JUnit5Test {
    
    @TestFactory
    List<DynamicNode> testFactory() {
        return List.of(
            dynamicContainer("Dynamic Container 1", List.of(
                dynamicTest("Foo", () -> System.out.println("Dynamic Foo.")),
                dynamicContainer("Dynamic Container 1-1", List.of(
                    dynamicTest("Hoge", () -> System.out.println("Dynamic Hoge.")),
                    dynamicTest("Fuga", () -> System.out.println("Dynamic Fuga."))
                ))
            )),
            dynamicContainer("Dynamic Container 2", List.of(
                dynamicTest("Fizz", () -> System.out.println("Dynamic Fizz.")),
                dynamicTest("Buzz", () -> System.out.println("Dynamic Buzz."))
            ))
        );
    }
}

Execution result


Dynamic Foo.
Dynamic Hoge.
Dynamic Fuga.
Dynamic Fizz.
Dynamic Buzz.

.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    '-- testFactory() [OK]
      +-- Dynamic Container 1 [OK]
      | +-- Foo [OK]
      | '-- Dynamic Container 1-1 [OK]
      |   +-- Hoge [OK]
      |   '-- Fuga [OK]
      '-- Dynamic Container 2 [OK]
        +-- Fizz [OK]
        '-- Buzz [OK]

-DynamicContainer allows you to nest dynamic tests

Parallel execution

junit-platform.properties


junit.jupiter.execution.parallel.enabled=true

--junit-platform.properties is placed in the class path root

package sample.junit5;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;

@Execution(ExecutionMode.CONCURRENT) //★ Don't forget to put this on!
class JUnit5Test {
    @BeforeAll
    static void beforeAll() {
        printThread("beforeAll()");
    }

    @BeforeEach
    void beforeEach(TestInfo testInfo) {
        String name = testInfo.getDisplayName();
        printThread("  " + name + ":beforeEach()");
    }
    
    @Test
    void test1() {
        printThread("    test1()");
    }

    @Test
    void test2() {
        printThread("    test2()");
    }

    @AfterEach
    void afterEach(TestInfo testInfo) {
        String name = testInfo.getDisplayName();
        printThread("  " + name + ":afterEach()");
    }
    
    @AfterAll
    static void afterAll() {
        printThread("afterAll()");
    }
    
    private static void printThread(String test) {
        String name = Thread.currentThread().getName();
        System.out.printf("%s@%s%n", test, name);
    }
}

Execution result


beforeAll()@ForkJoinPool-1-worker-3
  test1():beforeEach()@ForkJoinPool-1-worker-5
  test2():beforeEach()@ForkJoinPool-1-worker-7
    test2()@ForkJoinPool-1-worker-7
    test1()@ForkJoinPool-1-worker-5
  test1():afterEach()@ForkJoinPool-1-worker-5
  test2():afterEach()@ForkJoinPool-1-worker-7
afterAll()@ForkJoinPool-1-worker-3

--By default, all test methods are executed sequentially in a single thread --If you set junit.jupiter.execution.parallel.enabled = true in the configuration file (junit-platform.properties placed in the class path root), parallel execution will be enabled. --However, this setting alone does not change the test method and it is executed sequentially in a single thread. --In order to execute test methods in parallel, @Execution annotation Must be set to value to specify CONCURRENT -If SAME_THREAD is specified, it will be executed in the same thread as the parent. --The default is this setting

Change the default run mode

junit-platform.properties


junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
package sample.junit5;

import org.junit.jupiter.api.Test;

class JUnit5Test {
    
    @Test
    void test1() {
        printThread("test1()");
    }

    @Test
    void test2() {
        printThread("test2()");
    }
    
    private static void printThread(String test) {
        String name = Thread.currentThread().getName();
        System.out.printf("%s@%s%n", test, name);
    }
}

Execution result


test1()@ForkJoinPool-1-worker-5
test2()@ForkJoinPool-1-worker-7

--If you specify junit.jupiter.execution.parallel.mode.default = concurrent in the configuration file, the default execution mode will be parallel execution (with ʻExecutionMode.CONCURRENT` specified).

Exceptions to which the default run mode does not apply

--If you specify junit.jupiter.execution.parallel.mode.default = concurrent, most test methods will be executed in parallel. --However, as an exception, test classes and methods with the following settings are not executed in parallel even if the default execution mode is changed. --Test class with Lifecycle.PER_CLASS --Test method with MethodOrderer other than MethodOrderer.Random

Example of not executing in parallel even if the default execution mode is changed


package sample.junit5;

import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestMethodOrder;

class JUnit5Test {

    @Nested
    class StandardNestedTest {

        @Test
        void test1() {
            printThread("StandardNestedTest.test1()");
        }

        @Test
        void test2() {
            printThread("StandardNestedTest.test2()");
        }
    }
    
    @Nested
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    class PerClassTest {

        @Test
        void test1() {
            printThread("PerClassTest.test1()");
        }

        @Test
        void test2() {
            printThread("PerClassTest.test2()");
        }
    }
    
    @Nested
    @TestMethodOrder(MethodOrderer.Alphanumeric.class)
    class OrderedTest {

        @Test
        void test1() {
            printThread("OrderedTest.test1()");
        }

        @Test
        void test2() {
            printThread("OrderedTest.test2()");
        }
    }
    
    private static void printThread(String test) {
        String name = Thread.currentThread().getName();
        System.out.printf("%s@%s%n", test, name);
    }
}

Execution result


StandardNestedTest.test1()@ForkJoinPool-1-worker-15
StandardNestedTest.test2()@ForkJoinPool-1-worker-13
PerClassTest.test1()@ForkJoinPool-1-worker-7
OrderedTest.test1()@ForkJoinPool-1-worker-5
PerClassTest.test2()@ForkJoinPool-1-worker-7
OrderedTest.test2()@ForkJoinPool-1-worker-5

--The test method of StandardNestedTest is running in a different thread --But the test methods for PerClassTest and ʻOrderedTestare each running in the same thread. --If you want to execute the test methods of these classes in parallel [^ 5], make sure that the test class is thread-safe, and then explicitly specify@Execution (ExecutionMode.CONCURRENT)`.

[^ 5]: It's a strange story to run tests in parallel with the order specified by @TestMethodOrder.

Explicitly specify parallel execution


package sample.junit5;

...
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;

class JUnit5Test {

    @Nested
    class StandardNestedTest {
        ...
    }
    
    @Nested
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    @Execution(ExecutionMode.CONCURRENT)
    class PerClassTest {
        ...
    }
    
    @Nested
    @TestMethodOrder(MethodOrderer.Alphanumeric.class)
    @Execution(ExecutionMode.CONCURRENT)
    class OrderedTest {
        ...
    }
    
    private static void printThread(String test) {...}
}

Execution result


PerClassTest.test1()@ForkJoinPool-1-worker-15
StandardNestedTest.test1()@ForkJoinPool-1-worker-13
PerClassTest.test2()@ForkJoinPool-1-worker-7
OrderedTest.test1()@ForkJoinPool-1-worker-11
OrderedTest.test2()@ForkJoinPool-1-worker-5
StandardNestedTest.test2()@ForkJoinPool-1-worker-9

Change the default run mode for top-level classes

junit-platform.properties


junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.classes.default=concurrent

FooTest.java


package sample.junit5;

import org.junit.jupiter.api.Test;

class FooTest {

    @Test
    void test1() {
        printThread("FooTest.test1()");
    }

    @Test
    void test2() {
        printThread("FooTest.test2()");
    }
    
    private static void printThread(String test) {
        String name = Thread.currentThread().getName();
        System.out.printf("%s@%s%n", test, name);
    }
}

BarTest.java


package sample.junit5;

import org.junit.jupiter.api.Test;

class BarTest {

    @Test
    void test1() {
        printThread("BarTest.test1()");
    }

    @Test
    void test2() {
        printThread("BarTest.test2()");
    }
    
    private static void printThread(String test) {
        String name = Thread.currentThread().getName();
        System.out.printf("%s@%s%n", test, name);
    }
}

Execution result


BarTest.test1()@ForkJoinPool-1-worker-3
FooTest.test1()@ForkJoinPool-1-worker-7
BarTest.test2()@ForkJoinPool-1-worker-3
FooTest.test2()@ForkJoinPool-1-worker-7

--If you specify junit.jupiter.execution.parallel.mode.classes.default = concurrent in the config file, you can only change the default execution mode of top-level classes. --The junit.jupiter.execution.parallel.mode.default = concurrent that you set earlier controls method-level parallel execution, and this time it is not set (hence the default same_thread. Same state as specified) --As a result, each test class is executed in a separate thread, and the methods in the test class are executed sequentially in a single thread. --FooTest and BarTest are running in different threads (ForkJoinPool-1-worker-7 and ForkJoinPool-1-worker-3) --But each method in FooTest is executed in one thread (ForkJoinPool-1-worker-7) --There are a total of 4 combinations of settings for junit.jupiter.execution.parallel.mode.default and junit.jupiter.execution.parallel.mode.classes.default, and each works as follows.

junit5.jpg

--If junit.jupiter.execution.parallel.mode.classes.default (default behavior for each class) is not specified, the same setting value as junit.jupiter.execution.parallel.mode.default become

Adjust the number of simultaneous parallels

Dynamically change according to the number of processors (cores)

junit-platform.properties


jjunit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=dynamic
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.dynamic.factor=2

ParallelismCounter


package sample.junit5;

import java.util.concurrent.atomic.AtomicInteger;

public class ParallelismCounter {
    private final AtomicInteger counter = new AtomicInteger(0);
    private final AtomicInteger max = new AtomicInteger(0);
    
    public void increment() {
        this.max.set(Math.max(this.max.get(), this.counter.incrementAndGet()));
    }
    
    public void decrement() {
        this.counter.decrementAndGet();
    }
    
    public int getMaxCount() {
        return this.max.get();
    }
}

--Class for counting the number of threads running concurrently

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;

import java.util.stream.IntStream;
import java.util.stream.Stream;

class JUnit5Test {
    private long begin;
    private ParallelismCounter counter = new ParallelismCounter();
    
    @BeforeEach
    void beforeEach() {
        begin = System.currentTimeMillis();
    }
    
    @TestFactory
    Stream<DynamicNode> testFactory() {
        return IntStream
                .range(0, 20)
                .mapToObj(i -> DynamicTest.dynamicTest("test" + i, () -> {
                    counter.increment();
                    Thread.sleep(1000);
                    counter.decrement();
                }));
    }
    
    @AfterEach
    void printThreadNames() {
        System.out.println(System.currentTimeMillis() - begin + "ms");
        System.out.println("Available Processors = " + Runtime.getRuntime().availableProcessors());
        System.out.println("Max Parallelism Count = " + counter.getMaxCount());
        System.out.println("Active Thread Count = " + Thread.activeCount());
        Thread[] activeThreads = new Thread[Thread.activeCount()];
        Thread.enumerate(activeThreads);
        IntStream.range(0, activeThreads.length)
                .mapToObj(i -> "[" + i + "] " + activeThreads[i].getName())
                .forEach(System.out::println);
    }
}

--Using the dynamic test mechanism, we are creating 20 tests that take 1 second. --Various information is output after the test is completed.

Execution result


2033ms
Available Processors = 8
Max Parallelism Count = 16
Active Thread Count = 18
[0] main
[1] ForkJoinPool-1-worker-19
[2] ForkJoinPool-1-worker-5
[3] ForkJoinPool-1-worker-23
[4] ForkJoinPool-1-worker-9
[5] ForkJoinPool-1-worker-27
[6] ForkJoinPool-1-worker-13
[7] ForkJoinPool-1-worker-31
[8] ForkJoinPool-1-worker-17
[9] ForkJoinPool-1-worker-3
[10] ForkJoinPool-1-worker-21
[11] ForkJoinPool-1-worker-7
[12] ForkJoinPool-1-worker-25
[13] ForkJoinPool-1-worker-11
[14] ForkJoinPool-1-worker-29
[15] ForkJoinPool-1-worker-15
[16] ForkJoinPool-1-worker-1
[17] ForkJoinPool-1-worker-33

--You can adjust the number of threads running in parallel by specifying junit.jupiter.execution.parallel.config.dynamic.factor in the config file. --The number of threads executed in parallel is the number obtained by multiplying the value specified in factor by the number of processors (cores) in the execution environment. --In the verified environment, the number of cores was 8 and 2 was specified for factor, so the number of threads executed in parallel is 16. --In addition, ForkJoinPool is used to execute parallel execution. And the number of threads pooled can be greater than the number of threads running in parallel (the number of active threads is greater than the number of threads running in parallel) --If factor is not specified, the default is 1. --By the way, factor is converted to BigDecimal behind the scenes, so you can specify a small number. --Truncate after the decimal point (finally intValue () Converted to ʻint`)

junit-paltform.properties


junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=dynamic
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.dynamic.factor=0.5

Execution result


5042ms
Available Processors = 8
Max Parallelism Count = 4
Active Thread Count = 6
[0] main
[1] ForkJoinPool-1-worker-3
[2] ForkJoinPool-1-worker-5
[3] ForkJoinPool-1-worker-7
[4] ForkJoinPool-1-worker-1
[5] ForkJoinPool-1-worker-9

Set to a fixed value

junit-platform.properties


junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism=6

Execution result


4031ms
Available Processors = 8
Max Parallelism Count = 6
Active Thread Count = 8
[0] main
[1] ForkJoinPool-1-worker-3
[2] ForkJoinPool-1-worker-5
[3] ForkJoinPool-1-worker-7
[4] ForkJoinPool-1-worker-9
[5] ForkJoinPool-1-worker-11
[6] ForkJoinPool-1-worker-13
[7] ForkJoinPool-1-worker-15

--If you specify fixed in junit.jupiter.execution.parallel.config.strategy, you can specify a fixed number of parallels. --The number of parallels is specified by junit.jupiter.execution.parallel.config.fixed.parallelism

Customize as you like

build.gradle


...
dependencies {
    testImplementation "org.junit.jupiter:junit-jupiter:5.5.2"
    testImplementation "org.junit.jupiter:junit-jupiter-engine:5.5.2" // ★
}

-- junit-jupiter-engine can be referenced only at runtime by default, so it is specified in testImplementation so that it can also be referenced at compile time.

MyParallelExecutionConfigurationStrategy


package sample.junit5;

import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfiguration;
import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfigurationStrategy;

public class MyParallelExecutionConfigurationStrategy implements ParallelExecutionConfigurationStrategy {
    
    @Override
    public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) {
        return new ParallelExecutionConfiguration() {

            @Override
            public int getParallelism() {
                return 7;
            }

            @Override
            public int getMinimumRunnable() {
                return 7;
            }

            @Override
            public int getMaxPoolSize() {
                return 7;
            }

            @Override
            public int getCorePoolSize() {
                return 7;
            }

            @Override
            public int getKeepAliveSeconds() {
                return 30;
            }
        };
    }
}

--Create a class that implements ParallelExecutionConfigurationStrategy --The createConfiguration () method returns an instance that implements ParallelExecutionConfiguration. --The Getter defined in ParallelExecutionConfiguration is used when generating ForkJoinPool. --The meaning of each value is [Javadoc of constructor] of ForkJoinPool (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/ForkJoinPool.html" #% 3Cinit% 3E (int, java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory, java.lang.Thread.UncaughtExceptionHandler, boolean, int, int, int, java.util.function.Predicate, long, java.util.concurrent. See TimeUnit))

junit-platform.properties


junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=custom
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.custom.class=sample.junit5.MyParallelExecutionConfigurationStrategy

--Specify custom in junit.jupiter.execution.parallel.config.strategy --Specify the implementation class of your own ParallelExecutionConfigurationStrategy with junit.jupiter.execution.parallel.config.custom.class

Execution result


4034ms
Available Processors = 8
Max Parallelism Count = 7
Active Thread Count = 8
[0] main
[1] ForkJoinPool-1-worker-3
[2] ForkJoinPool-1-worker-5
[3] ForkJoinPool-1-worker-7
[4] ForkJoinPool-1-worker-9
[5] ForkJoinPool-1-worker-11
[6] ForkJoinPool-1-worker-13
[7] ForkJoinPool-1-worker-15

--You can see that they are executed in parallel according to the setting of ParallelExecutionConfiguration returned by MyParallelExecutionConfigurationStrategy.

exclusion control

When there is no exclusive control


package sample.junit5;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;

@Execution(ExecutionMode.CONCURRENT)
class JUnit5Test {
    
    static int n = 0;
    
    @Test
    void test1() throws Exception {
        process("test1");
    }
    
    @Test
    void test2() throws Exception {
        process("test2");
    }

    @Test
    void test3() throws Exception {
        process("test3");
    }
    
    private void process(String name) {
        System.out.println("begin " + name);
        for (int i=0; i<10000; i++) {
            n++;
        }
        System.out.println("end " + name);
    }
    
    @AfterAll
    static void afterAll() {
        System.out.println("n = " + n);
    }
}

--The class is executed in parallel and the static variable n is incremented 10,000 times from each of the three test methods.

Execution result


begin test3
begin test1
begin test2
end test1
end test2
end test3
n = 13394

--Of course, the result will not be 30,000 because it is out of sync.

When exclusive control is put


package sample.junit5;

...
import org.junit.jupiter.api.parallel.ResourceLock;

@Execution(ExecutionMode.CONCURRENT)
class JUnit5Test {
    
    static int n = 0;
    
    @Test
    @ResourceLock("hoge")
    void test1() throws Exception { ... }
    
    @Test
    @ResourceLock("hoge")
    void test2() throws Exception { ... }

    @Test
    @ResourceLock("hoge")
    void test3() throws Exception { ... }
    
    private void process(String name) { ... }
    
    @AfterAll
    static void afterAll() { ... }
}

--Set @ResourceLock annotation to each method

Execution result


begin test1
end test1
begin test2
end test2
begin test3
end test3
n = 30000

--The execution of each method was synchronized and the value of n became 30,000. -If you set @ResourceLock to a class or method, the test cases in it will be Will be synchronized --Specify the key string for controlling exclusion in value --Synchronized between @ResourceLock with the same key string

Specify the access mode

package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.jupiter.api.parallel.ResourceAccessMode;
import org.junit.jupiter.api.parallel.ResourceLock;

@Execution(ExecutionMode.CONCURRENT)
class JUnit5Test {
    
    @Test   
    @ResourceLock(value = "hoge", mode = ResourceAccessMode.READ_WRITE)
    void test1() throws Exception {
        process("test1(READ_WRITE)");
    }
    
    @Test
    @ResourceLock(value = "hoge", mode = ResourceAccessMode.READ)
    void test2() throws Exception {
        process("test2(READ)");
    }

    @Test
    @ResourceLock(value = "hoge", mode = ResourceAccessMode.READ)
    void test3() throws Exception {
        process("test3(READ)");
    }
    
    private void process(String name) throws Exception {
        System.out.println("begin " + name);
        Thread.sleep(500);
        System.out.println("end " + name);
    }
}

Execution result


begin test1(READ_WRITE)
end test1(READ_WRITE)
begin test2(READ)
begin test3(READ)
end test2(READ)
end test3(READ)

--You can specify the access mode with mode of @ResourceLock --Access mode is specified by the constant defined in ResourceAccessMode --The combination of exclusive control by each access mode looks like the following.

junit5.jpg

--It is possible to execute in parallel between READs, but exclusive control is performed when READ_WRITE is involved. --READ is specified for the test method that only reads the data and does not update, and READ_WRITE is specified for the test method that updates the data.

Use the default method of the interface

DefaultMethodTest


package sample.junit5;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.List;

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

interface DefaultMethodTest {
    @BeforeAll
    static void beforeAll() {
        System.out.println("beforeAll()");
    }

    @BeforeEach
    default void beforeEach() {
        System.out.println("  beforeEach()");
    }
    
    @Test
    default void test() {
        System.out.println("    test()");
    }
    
    @RepeatedTest(3)
    default void repeatedTest() {
        System.out.println("    repeatedTest()");
    }
    
    @ParameterizedTest
    @ValueSource(strings = {"one", "two", "three"})
    default void parameterizedTest(String param) {
        System.out.println("    parameterizedTest(" + param + ")");
    }
    
    @TestFactory
    default List<DynamicNode> testFactory() {
        return List.of(
            dynamicTest("DynamicTest1", () -> System.out.println("    testFactory(1)")),
            dynamicTest("DynamicTest2", () -> System.out.println("    testFactory(2)"))
        );
    }
    
    @AfterEach
    default void afterEach() {
        System.out.println("  afterEach()");
    }
    
    @AfterAll
    static void afterAll() {
        System.out.println("afterAll()");
    }
}

JUnit5Test


package sample.junit5;

class JUnit5Test implements DefaultMethodTest {}

Execution result


beforeAll()
  beforeEach()
    repeatedTest()
  afterEach()
  beforeEach()
    repeatedTest()
  afterEach()
  beforeEach()
    repeatedTest()
  afterEach()
  beforeEach()
    testFactory(1)
    testFactory(2)
  afterEach()
  beforeEach()
    test()
  afterEach()
  beforeEach()
    parameterizedTest(one)
  afterEach()
  beforeEach()
    parameterizedTest(two)
  afterEach()
  beforeEach()
    parameterizedTest(three)
  afterEach()
afterAll()

--You can also define tests with the default methods of the interface --If you ʻimplements` the interface in the target class, the test will be executed.

Extended model

JUnit Jupiter has a mechanism called ** extension model **, which makes it easy to introduce any extension.

Hello World

MyExtension


package sample.junit5;

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class MyExtension implements BeforeEachCallback, AfterEachCallback {
    
    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        System.out.println("MyExtension.beforeEach()");
    }

    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        System.out.println("MyExtension.afterEach()");
    }
}

--To create an extension, first create a class that implements the Extension interface To do --However, ʻExtension itself is a marker interface and no method is defined. --Actually, [BeforeEachCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/BeforeEachCallback.html) and [AfterEachCallback](afterEachCallback](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/BeforeEachCallback.html) which inherited ʻExtension Implement subinterfaces defined for each extension point, such as https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/AfterEachCallback.html) --For BeforeEachCallback, thebeforeEach ()method that is called back before each test is defined. ʻAfterEachCallback defines a ʻafterEach () method that will be called back after each test.

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MyExtension.class)
class JUnit5Test {

    @Test
    void test1() {
        System.out.println("  test1()");
    }

    @Test
    void test2() {
        System.out.println("  test2()");
    }
}

--As one way to actually use a class that implements an extension in a test, [@ExtendWith](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension /ExtendWith.html) There is a way to use annotations --Set the @ExtendWith annotation where you want to apply the extension, and specify the Class object of the extension you want to apply to value.

Execution result


MyExtension.beforeEach()
  test1()
MyExtension.afterEach()
MyExtension.beforeEach()
  test2()
MyExtension.afterEach()

--With this alone, the processing defined by the extension function will be executed before and after each test. --In the example, @ExtendWith was specified for the class, but the extension can be partially applied by specifying it in the method.

test1()If you apply the extension only to the method


package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

class JUnit5Test {

    @Test
    @ExtendWith(MyExtension.class)
    void test1() {
        System.out.println("  test1()");
    }

    @Test
    void test2() {
        System.out.println("test2()");
    }
}

Execution result


MyExtension.beforeEach()
  test1()
MyExtension.afterEach()
test2()

Expansion point

ʻExtension` Interfaces that are extension points that inherit from the interface are as follows.

interface Description
ExecutionCondition Controls whether the test is run.
TestInstanceFactory Create a test instance.
TestInstancePostProcessor Perform initialization processing after test instance generation.
ParameterResolver Resolve arguments such as test methods and lifecycle methods.
BeforeAllCallback
BeforeEachCallback
BeforeTestExecutionCallback
AfterTestExecutionCallback
AfterEachCallback
AfterAllCallback
Execute processing along the life cycle, such as before and after executing the test.
TestWatcher Execute post-processing according to the execution result of the test method.
TestExecutionExceptionHandler Handle exceptions thrown during test execution.
LifecycleMethodExecutionExceptionHandler @BeforeEachHandle exceptions thrown by lifecycle methods such as.
TestTemplateInvocationContextProvider Performs preparatory processing to execute the same test in different contexts.

Support class

Utility classes (support classes) that can be used universally when implementing extension classes are provided.

Exception handling and troublesome description are omitted, and the operation can be performed in a concise manner.

For the time being, keep in mind that "this kind of thing exists", and when you start creating an extension class, think "Oh, can you use this support class?" And look for the method you want. I think it's good.

These classes are not internal utilities, they are provided as an aid when a third party creates their own TestEngine or extension, so you can use them with confidence.

Control test execution conditions

MyExecutionCondition


package sample.junit5.extension;

import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;

public class MyExecutionCondition implements ExecutionCondition {
    
    @Override
    public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
        if (context.getTestMethod().isPresent()) {
            System.out.println("# Test method = " + context.getRequiredTestMethod().getName());
            return context.getDisplayName().contains("o")
                    ? ConditionEvaluationResult.enabled("Test name has 'o'.")
                    : ConditionEvaluationResult.disabled("Test name does not have 'o'.");
        } else {
            System.out.println("# Test class = " + context.getRequiredTestClass().getSimpleName());
            return ConditionEvaluationResult.enabled("This is test class.");
        }
    }
}

--Only valid test methods that include " o " in their display name

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyExecutionCondition;

import java.util.List;

@ExtendWith(MyExecutionCondition.class)
class JUnit5Test {

    @BeforeEach
    void beforeEach() {
        System.out.println("beforeEach()");
    }
    
    @Test
    void hoge() {
        System.out.println("  hoge()");
    }

    @Test
    void fuga() {
        System.out.println("  fuga()");
    }
    
    @Nested
    class NestedClass {
        
        @Test
        void piyo() {
            System.out.println("  piyo()");
        }
    }
    
    @TestFactory
    List<DynamicNode> testFactory() {
        return List.of(
            DynamicTest.dynamicTest("DynamicTest1", () -> System.out.println("  dynamicTest1")),
            DynamicTest.dynamicTest("DynamicTest2", () -> System.out.println("  dynamicTest2"))
        );
    }
}

Execution result


# Test class = JUnit5Test
# Test method = testFactory
beforeEach()
  dynamicTest1
  dynamicTest2
# Test method = fuga
# Test method = hoge
beforeEach()
  hoge()
# Test class = NestedClass
# Test method = piyo
beforeEach()
  piyo()

--ExecutionCondition allows you to control whether the test is run --Since the ʻevaluateExecutionCondition (ExtensionContext) method is called for each container [^ 6] and method, the return value controls whether to execute the target test. --[ConditionEvaluationResult.enabled (String)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ConditionEvaluationResult.html#enabled (java.lang.) Returns the value generated by String)) --If not executed, [ConditionEvaluationResult.disabled (String)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ConditionEvaluationResult.html#disabled (java.lang. Returns the value generated by String)) --In the argument, describe the reason why the target was enabled / disabled. --ʻEvaluateExecutionCondition (ExtensionContext) If you use ExtensionContext that the method receives as an argument, You can see information about the target test method or container -Some methods such as getTestMethod () depend on the condition The return type is ʻOptionalbecause the value may not exist in --If you know that it will never benull, then [getRequiredTestMethod ()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext. You can also use a method with Required` like html # getRequiredTestMethod ())

[^ 6]: The documentation says container, but it doesn't say exactly what container refers to (probably something like DynamicContainer for test classes or dynamic tests). I think it refers to the catamari that summarizes the test methods)

Create a test instance

MyTestInstanceFactory


package sample.junit5.extension;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestInstanceFactory;
import org.junit.jupiter.api.extension.TestInstanceFactoryContext;
import org.junit.jupiter.api.extension.TestInstantiationException;
import org.junit.platform.commons.support.ReflectionSupport;

public class MyTestInstanceFactory implements TestInstanceFactory {
    @Override
    public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) throws TestInstantiationException {
        Class<?> testClass = factoryContext.getTestClass();
        System.out.println("===========================================");
        System.out.println("* testClass=" + testClass);
        System.out.println("* outerInstance=" + factoryContext.getOuterInstance().orElse("<empty>"));

        
        return factoryContext
                .getOuterInstance()
                .map(outerInstance -> {
                    Object instance = ReflectionSupport.newInstance(testClass, outerInstance);
                    System.out.println("* outerInstance [" + outerInstance.hashCode() + "]");
                    System.out.println("* testInstance [" + instance.hashCode() + "]");
                    return instance;
                })
                .orElseGet(() -> {
                    Object instance = ReflectionSupport.newInstance(testClass);
                    System.out.println("* testInstance [" + instance.hashCode() + "]");
                    return instance;
                });
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyTestInstanceFactory;

import java.util.List;

@ExtendWith(MyTestInstanceFactory.class)
class JUnit5Test {
    
    @Test
    void test() {
        System.out.println("JUnit5Test.test() [" + this.hashCode() + "]");
    }
    
    @Nested
    class NestedClass {
        
        @Test
        void test() {
            System.out.println("NestedClass.test() [" + this.hashCode() + "]");
        }
    }
    
    @TestFactory
    List<DynamicNode> testFactory() {
        return List.of(
            DynamicTest.dynamicTest("DynamicTest1", () -> System.out.println("JUnit5Test.dynamicTest1() [" + this.hashCode() + "]")),
            DynamicTest.dynamicTest("DynamicTest2", () -> System.out.println("JUnit5Test.dynamicTest2() [" + this.hashCode() + "]"))
        );
    }
}

Execution result


===========================================
* testClass=class sample.junit5.JUnit5Test
* outerInstance=<empty>
* testInstance [1781155104]
JUnit5Test.dynamicTest1() [1781155104]
JUnit5Test.dynamicTest2() [1781155104]
===========================================
* testClass=class sample.junit5.JUnit5Test
* outerInstance=<empty>
* testInstance [667449440]
JUnit5Test.test() [667449440]
===========================================
* testClass=class sample.junit5.JUnit5Test
* outerInstance=<empty>
* testInstance [1846668301]
===========================================
* testClass=class sample.junit5.JUnit5Test$NestedClass
* outerInstance=sample.junit5.JUnit5Test@6e11ec0d
* outerInstance [1846668301]
* testInstance [1282836833]
NestedClass.test() [1282836833]

-TestInstanceFactory can be used to manualize the generation of test instances. --The createTestInstance () method is called before each test method is executed -* If the life cycle is set to PER_CLASS, it will be called only once for each class. --The instance returned by createTestInstance () will be used in the test --If there is an internal test class defined by @ Nested, it will be called once to generate the outer class, and then the method will be called again to generate the internal test class. --In the case of an inner class, getOuterInstance () of TestInstanceFactoryContext is not empty, so you can judge by that.

Perform initialization processing after test instance generation

MyTestInstancePostProcessor


package sample.junit5.extension;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;

public class MyTestInstancePostProcessor implements TestInstancePostProcessor {
    
    @Override
    public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
        System.out.println("testInstance.hash = " + testInstance.hashCode());
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyTestInstancePostProcessor;

@ExtendWith(MyTestInstancePostProcessor.class)
class JUnit5Test {
    
    @Test
    void test1() {
        System.out.println("test1() [" + this.hashCode() + "]");
    }

    @Test
    void test2() {
        System.out.println("test2() [" + this.hashCode() + "]");
    }
}

Execution result


testInstance.hash = 756008141
test1() [756008141]
testInstance.hash = 282044315
test2() [282044315]

-Use TestInstancePostProcessor to get a test instance before the test method is executed. Can be received and perform any processing --It seems to be used to inject some dependency into the test instance or call an initialization method. ――Isn't it okay with BeforeEachCallback? --If it is BeforeEachCallback, the test instance will be obtained from ʻExtensionContext, so I wonder if it is ʻOptional orgetRequiredInstance (). ――After that, there may be a merit that you can clarify the implementation intention of "performing post-processing for the test instance" (speculation).

Resolve parameters

MyParameterResolver


package sample.junit5.extension;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;

import java.lang.reflect.Executable;
import java.lang.reflect.Parameter;
import java.util.Optional;

public class MyParameterResolver implements ParameterResolver {
    
    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
        Executable executable = parameterContext.getDeclaringExecutable();
        int index = parameterContext.getIndex();
        Parameter parameter = parameterContext.getParameter();
        Optional<Object> target = parameterContext.getTarget();

        System.out.printf(
            "target=%s, executable=%s, index=%d, parameter=%s%n",
            target.orElse("<empty>"),
            executable.getName(),
            index,
            parameter.getName()
        );
        
        return true;
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
        Class<?> type = parameterContext.getParameter().getType();
        if (type.equals(String.class)) {
            return "Hello";
        } else if (type.equals(int.class)) {
            return 999;
        } else {
            return 12.34;
        }
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyParameterResolver;

import java.util.List;

@ExtendWith(MyParameterResolver.class)
class JUnit5Test {

    @BeforeEach
    void beforeEach(int i) {
        System.out.printf("beforeEach(i=%d)%n", i);
    }

    @TestFactory
    List<DynamicNode> dynamicTest(String string) {
        return List.of(DynamicTest.dynamicTest("DynamicTest", () -> System.out.printf("dynamicTest(string=%s)%n", string)));
    }
    
    @Test
    void test1(String string, double d, int i) {
        System.out.printf("test1(string=%s, d=%f, i=%d)%n", string, d, i);
    }
    
    @Nested
    class NestedClass {
        
        @Test
        void test2(double d) {
            System.out.printf("test2(d=%f)%n", d);
        }
    }
    
    @AfterEach
    void afterEach() {
        System.out.println("-----------------------------------------");
    }
}

Execution result


target=sample.junit5.JUnit5Test@5e101011, executable=beforeEach, index=0, parameter=i
beforeEach(i=999)
target=sample.junit5.JUnit5Test@5e101011, executable=dynamicTest, index=0, parameter=string
dynamicTest(string=Hello)
-----------------------------------------
target=sample.junit5.JUnit5Test@4d3b0c46, executable=beforeEach, index=0, parameter=i
beforeEach(i=999)
target=sample.junit5.JUnit5Test@4d3b0c46, executable=test1, index=0, parameter=string
target=sample.junit5.JUnit5Test@4d3b0c46, executable=test1, index=1, parameter=d
target=sample.junit5.JUnit5Test@4d3b0c46, executable=test1, index=2, parameter=i
test1(string=Hello, d=12.340000, i=999)
-----------------------------------------
target=sample.junit5.JUnit5Test@7d9ea927, executable=beforeEach, index=0, parameter=i
beforeEach(i=999)
target=sample.junit5.JUnit5Test$NestedClass@710edef, executable=test2, index=0, parameter=d
test2(d=12.340000)
-----------------------------------------

-ParameterResolver can be used to arbitrarily resolve the arguments of various methods. Become --Not only @Test but also method arguments such as @TestFactory and @BeforeEach can be resolved. --supportsParameter () is called for each method argument -From ParameterContext, you can refer to the meta information of the argument. --Implemented to return true if it supports argument resolution, otherwise it returns false --If supportsParameter () returns true, thenresolveParameter ()is called. --Implement this method to return the value of the resolved argument --Note that in order to get the argument name with Parameter.getName (), you need to add the -parameters option when compiling with javac. --With no options, names like ʻarg0, ʻarg1 --If you are building with Gradle, you can set it like compileTestJava.options.complierArgs + ="-parameters "

Perform processing along the life cycle

MyLifeCycleCallback


package sample.junit5.extension;

import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class MyLifeCycleCallback
    implements BeforeAllCallback,
               BeforeEachCallback,
               BeforeTestExecutionCallback,
               AfterTestExecutionCallback,
               AfterEachCallback,
               AfterAllCallback {
    
    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        System.out.println("BeforeAllCallback");
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        System.out.println("    BeforeEachCallback");
    }

    @Override
    public void beforeTestExecution(ExtensionContext context) throws Exception {
        System.out.println("        BeforeTestExecutionCallback");
    }

    @Override
    public void afterTestExecution(ExtensionContext context) throws Exception {
        System.out.println("        AfterTestExecutionCallback");
    }

    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        System.out.println("    AfterEachCallback");
    }

    @Override
    public void afterAll(ExtensionContext context) throws Exception {
        System.out.println("AfterAllCallback");
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyLifeCycleCallback;

@ExtendWith(MyLifeCycleCallback.class)
class JUnit5Test {
    @BeforeAll
    static void beforeAll() {
        System.out.println("  beforeAll()");
    }
    
    @BeforeEach
    void beforeEach() {
        System.out.println("      beforeEach()");
    }
    
    @Test
    void test() {
        System.out.println("          test()");
    }
    
    @AfterEach
    void afterEach() {
        System.out.println("      afterEach()");
    }
    
    @AfterAll
    static void afterAll() {
        System.out.println("  afterAll()");
    }
}

Execution result


BeforeAllCallback
  beforeAll()
    BeforeEachCallback
      beforeEach()
        BeforeTestExecutionCallback
          test()
        AfterTestExecutionCallback
      afterEach()
    AfterEachCallback
  afterAll()
AfterAllCallback

Perform processing according to the test result

MyTestWatcher


package sample.junit5.extension;

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestWatcher;

import java.util.Optional;

public class MyTestWatcher implements TestWatcher, AfterEachCallback {

    @Override
    public void testDisabled(ExtensionContext context, Optional<String> reason) {
        System.out.println("disabled : test=" + context.getDisplayName() + ", reason=" + reason.orElse("<empty>"));
    }

    @Override
    public void testSuccessful(ExtensionContext context) {
        System.out.println("successful : test=" + context.getDisplayName());
    }

    @Override
    public void testAborted(ExtensionContext context, Throwable cause) {
        System.out.println("aborted : test=" + context.getDisplayName() + ", cause=" + cause);
    }

    @Override
    public void testFailed(ExtensionContext context, Throwable cause) {
        System.out.println("failed : test=" + context.getDisplayName() + ", cause=" + cause);
    }

    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        System.out.println("AfterEachCallback");
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyTestWatcher;

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

@ExtendWith(MyTestWatcher.class)
class JUnit5Test {
    
    @Test
    void testSuccessful() {
        System.out.println("testSuccessful()");
        assertEquals(10, 10);
    }
    
    @Test
    void testFailed() {
        System.out.println("testFailed()");
        assertEquals(10, 20);
    }
    
    @Test
    @Disabled("REASON")
    void testDisabled() {
        System.out.println("testDisabled()");
    }
    
    @Test
    void testAborted() {
        System.out.println("testAborted()");
        assumeTrue(false, "test abort");
    }
}

Execution result


testAborted()
AfterEachCallback
aborted : test=testAborted(), cause=org.opentest4j.TestAbortedException: Assumption failed: test abort

testSuccessful()
AfterEachCallback
successful : test=testSuccessful()

disabled : test=testDisabled(), reason=REASON

testFailed()
AfterEachCallback
failed : test=testFailed(), cause=org.opentest4j.AssertionFailedError: expected: <10> but was: <20>

-TestWatcher can be used to implement processing according to the test result. --The following four methods are defined in TestWatcher - testDisabled() --Run when the test was invalid - testSuccessful() --Run when the test is successful - testAborted() --Run when the test is interrupted (such as ʻassumeThat ()) - testFailed() --Run when the test fails --Each method is defined as an emptydefault method --Therefore, nothing is done by default --Write a concrete implementation by overriding each method as needed --Each method is executed after ʻAfterEachCallback

Handle exceptions thrown in the test

MyTestExecutionExceptionHandler


package sample.junit5.extension;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;

public class MyTestExecutionExceptionHandler implements TestExecutionExceptionHandler {
    
    @Override
    public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
        System.out.println(" * throwable=" + throwable);
        if (throwable instanceof NullPointerException) {
            throw throwable;
        } else if (throwable instanceof IllegalStateException) {
            throw new UnsupportedOperationException("test");
        }
    }
}

--If you receive a NullPointerException, rethrow it as is --Throw ʻUnsupportedOperationException if ʻIllegalStateException is received --Other than that, it ends without doing anything

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyTestExecutionExceptionHandler;

import java.io.IOException;

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

@ExtendWith(MyTestExecutionExceptionHandler.class)
class JUnit5Test {
    
    @Test
    void success() {
        System.out.println("success()");
        assertEquals(10, 10);
    }
    
    @Test
    void fail() {
        System.out.println("fail()");
        assertEquals(10, 20);
    }
    
    @Test
    void throwsIOException() throws Exception {
        System.out.println("throwsIOException()");
        throw new IOException("test");
    }
    
    @Test
    void throwsNullPointerException() {
        System.out.println("throwsNullPointerException()");
        throw new NullPointerException("test");
    }

    @Test
    void throwsIllegalStateException() {
        System.out.println("throwsIllegalStateException()");
        throw new IllegalStateException("test");
    }
}

Execution result


success()
fail()
 * throwable=org.opentest4j.AssertionFailedError: expected: <10> but was: <20>
throwsNullPointerException()
 * throwable=java.lang.NullPointerException: test
throwsIllegalStateException()
 * throwable=java.lang.IllegalStateException: test
throwsIOException()
 * throwable=java.io.IOException: test
.
'-- JUnit Jupiter [OK]
  +-- JUnit5Test [OK]
  | +-- success() [OK]
  | +-- fail() [OK]
  | +-- throwsNullPointerException() [X] test
  | +-- throwsIllegalStateException() [X] test
  | '-- throwsIOException() [OK]
  '-- ParallelismCheck [S] class sample.junit5.ParallelismCheck is @Disabled

Failures (2):
  JUnit Jupiter:JUnit5Test:throwsNullPointerException()
    MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'throwsNullPointerException', methodParameterTypes = '']
    => java.lang.NullPointerException: test
       ...

  JUnit Jupiter:JUnit5Test:throwsIllegalStateException()
    MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'throwsIllegalStateException', methodParameterTypes = '']
    => java.lang.UnsupportedOperationException: test

-TestExecutionExceptionHandler can be used to handle exceptions that occur in the test. --When an exception is thrown in the test, handleTestExecutionException () is called. --You can receive the exception thrown by the second argument --The test fails if this method throws an exception --If it exits without throwing any exceptions, the test succeeds (exceptions are squeezed) --It is also possible to throw an exception other than the one received --Be careful as assertion errors (ʻAssertionFailedError`) are also targeted. ――If you forget to re-throw, you will squeeze the assertion error.

Handle exceptions thrown by lifecycle methods

MyTestExecutionExceptionHandler


package sample.junit5.extension;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;

public class MyTestExecutionExceptionHandler implements TestExecutionExceptionHandler {
    
    @Override
    public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
        System.out.println("[TestExecutionExceptionHandler] throwable=" + throwable);
    }
}

MyLifecycleMethodExecutionExceptionHandler


package sample.junit5.extension;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler;

public class MyLifecycleMethodExecutionExceptionHandler implements LifecycleMethodExecutionExceptionHandler {

    @Override
    public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
        System.out.println("[LifecycleMethodExecutionExceptionHandler] throwable=" + throwable);
        throw throwable;
    }

    @Override
    public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
        System.out.println("[LifecycleMethodExecutionExceptionHandler] throwable=" + throwable);
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyLifecycleMethodExecutionExceptionHandler;
import sample.junit5.extension.MyTestExecutionExceptionHandler;

class JUnit5Test {
    
    @Nested
    class InnerClass1 {
        
        @Test
        @DisplayName("If the lifecycle method normally throws an exception")
        void test() {
            System.out.println("InnerClass1");
        }
    }

    @Nested
    @ExtendWith(MyTestExecutionExceptionHandler.class)
    class InnerClass2 {

        @Test
        @DisplayName("How TestExecutionExceptionHandler works for exceptions thrown in lifecycle methods")
        void test() {
            System.out.println("InnerClass2");
        }
    }

    @Nested
    @ExtendWith(MyLifecycleMethodExecutionExceptionHandler.class)
    class InnerClass3 {

        @Test
        @DisplayName("When the exception thrown by the lifecycle method is squeezed by LifecycleMethodExecutionExceptionHandler")
        void test() {
            System.out.println("InnerClass3");
        }
    }

    @Nested
    @ExtendWith(MyLifecycleMethodExecutionExceptionHandler.class)
    class InnerClass4 {

        @BeforeEach
        void beforeEach(TestInfo testInfo) {
            throw new RuntimeException("beforeEach@" + simpleClassName(testInfo));
        }
        
        @Test
        @DisplayName("If the LifecycleMethodExecutionExceptionHandler does not squeeze the exception thrown by the lifecycle method")
        void test() {
            System.out.println("InnerClass4");
        }
    }
    
    @AfterEach
    void afterEach(TestInfo testInfo) {
        throw new RuntimeException("afterEach@" + simpleClassName(testInfo));
    }
    
    static String simpleClassName(TestInfo testInfo) {
        return testInfo.getTestClass().map(Class::getSimpleName).orElse("<empty>");
    }
}

Execution result


[LifecycleMethodExecutionExceptionHandler] throwable=java.lang.RuntimeException: beforeEach@InnerClass4
[LifecycleMethodExecutionExceptionHandler] throwable=java.lang.RuntimeException: afterEach@InnerClass4
InnerClass3
[LifecycleMethodExecutionExceptionHandler] throwable=java.lang.RuntimeException: afterEach@InnerClass3
InnerClass2
InnerClass1

.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    +-- InnerClass4 [OK]
    | '--If the LifecycleMethodExecutionExceptionHandler does not squeeze the exception thrown by the lifecycle method[X] beforeEach@InnerClass4
    +-- InnerClass3 [OK]
    | '--When the exception thrown by the lifecycle method is squeezed by LifecycleMethodExecutionExceptionHandler[OK]
    +-- InnerClass2 [OK]
    | '--How TestExecutionExceptionHandler works for exceptions thrown in lifecycle methods[X] afterEach@InnerClass2
    '-- InnerClass1 [OK]
      '--If the lifecycle method normally throws an exception[X] afterEach@InnerClass1

-LifecycleMethodExecutionExceptionHandler can be used to handle exceptions raised by lifecycle methods. --TestExecutionExceptionHandler in the previous section can only be handled when an exception occurs in the test method body. --TestExecutionExceptionHandler is not called back when an exception is thrown in a lifecycle method --Four methods are defined in LifecycleMethodExecutionExceptionHandler --Each method corresponds to @BeforeAll, @BeforeEach, @AfterEach, @AfterAll, respectively. --When an exception is thrown in a lifecycle method, the corresponding method is called. --Each method is defined by the default method, and by default, the received exception is thrown again as it is. --Same as for TestExecutionExceptionHandler, you can squeeze the exception if you don't rethrow the exception received as an argument.

Run the same test in different contexts

MyTestTemplateInvocationContextProvider


package sample.junit5.extension;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;

import java.util.stream.Stream;

public class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider {
    @Override
    public boolean supportsTestTemplate(ExtensionContext context) {
        System.out.println("[supportsTestTemplate] displayName=" + context.getDisplayName());
        return context.getDisplayName().equals("test1()");
    }

    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
        System.out.println("[provideTestTemplateInvocationContexts] displayName=" + context.getDisplayName());
        return Stream.of(
            new MyTestTemplateInvocationContext(),
            new MyTestTemplateInvocationContext(),
            new MyTestTemplateInvocationContext()
        );
    }
    
    public static class MyTestTemplateInvocationContext implements TestTemplateInvocationContext {
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyTestTemplateInvocationContextProvider;

@ExtendWith(MyTestTemplateInvocationContextProvider.class)
class JUnit5Test {

    @TestTemplate
    void test1() {
        System.out.println("test1()");
    }

    @TestTemplate
    void test2() {
        System.out.println("test2()");
    }

    @Test
    void test3() {
        System.out.println("test3()");
    }
}

Execution result


[supportsTestTemplate] displayName=test1()
[provideTestTemplateInvocationContexts] displayName=test1()
test1()
test1()
test1()
[supportsTestTemplate] displayName=test2()
test3()

.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    +-- test1() [OK]
    | +-- [1] [OK]
    | +-- [2] [OK]
    | '-- [3] [OK]
    +-- test2() [X] You must register at least one TestTemplateInvocationContextProvider that supports @TestTemplate method [void sample.junit5.JUnit5Test.test2()]
    '-- test3() [OK]

Failures (1):
  JUnit Jupiter:JUnit5Test:test2()
    MethodSource [className = 'sample.junit5.JUnit5Test', methodName = 'test2', methodParameterTypes = '']
    => org.junit.platform.commons.PreconditionViolationException: You must register at least one TestTemplateInvocationContextProvider that supports @TestTemplate method [void sample.junit5.JUnit5Test.test2()]
       ...

--TestTemplateInvocationContextProvider allows you to execute the same test method multiple times in different contexts. --Tests you want to run in different contexts need to be annotated with @TestTemplate --supportsTestTemplate () of TestTemplateInvocationContextProvider is called for each method for which @TestTemplate is set. --Implement to return true when targeting (supporting) the target test --If you have set @TestTemplate but none of the supporting TestTemplateInvocationContextProvider exists, you will get an error. --If supported, provideTestTemplateInvocationContexts () will be called --Implement this method to return Stream of TestTemplateInvocationContext --TestTemplateInvocationContext represents one context when running a test --Construct Stream to return multiple elements when running in multiple contexts --In the above implementation example, Stream with 3MyTestTemplateInvocationContext is returned, so the test1 () method is executed 3 times (in 3 contexts).

Specify the display name in context

MyTestTemplateInvocationContextProvider


package sample.junit5.extension;

...

public class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider {
    ...

    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
        System.out.println("[provideTestTemplateInvocationContexts] displayName=" + context.getDisplayName());
        return Stream.of(
            new MyTestTemplateInvocationContext("Hoge"),
            new MyTestTemplateInvocationContext("Fuga"),
            new MyTestTemplateInvocationContext("Piyo")
        );
    }
    
    public static class MyTestTemplateInvocationContext implements TestTemplateInvocationContext {
        private final String name;

        public MyTestTemplateInvocationContext(String name) {
            this.name = name;
        }

        @Override
        public String getDisplayName(int invocationIndex) {
            return this.name + "[" + invocationIndex + "]";
        }
    }
}

Execution result


.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    +-- test1() [OK]
    | +-- Hoge[1] [OK]
    | +-- Fuga[2] [OK]
    | '-- Piyo[3] [OK]
    :

--A method called getDisplayName () is defined in TestTemplateInvocationContext. --This method is defined by the default method, and the default implementation returns " [" + invocationIndex + "]" . --ʻInvocationIndex` is passed the index of the current context (starting with 1) --By overriding this method, you can return any display name.

Add arbitrary extensions for each context

MyTestTemplateInvocationContextProvider


package sample.junit5.extension;

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;

import java.util.List;
import java.util.stream.Stream;

public class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider {
    ...

    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
        System.out.println("[provideTestTemplateInvocationContexts] displayName=" + context.getDisplayName());
        return Stream.of(
            new MyTestTemplateInvocationContext("BeforeEach", (BeforeEachCallback) ctx -> {
                System.out.println("beforeEachCallback()");
            }),
            new MyTestTemplateInvocationContext("AfterEach", (AfterEachCallback) ctx -> {
                System.out.println("afterEachCallback()");
            })
        );
    }
    
    public static class MyTestTemplateInvocationContext implements TestTemplateInvocationContext {
        private final String name;
        private final Extension extension;

        public MyTestTemplateInvocationContext(String name, Extension extension) {
            this.name = name;
            this.extension = extension;
        }

        @Override
        public String getDisplayName(int invocationIndex) {
            return this.name;
        }

        @Override
        public List<Extension> getAdditionalExtensions() {
            return List.of(this.extension);
        }
    }
}

Execution result


...
beforeEachCallback()
test1()
test1()
afterEachCallback()
...

.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    +-- test1() [OK]
    | +-- BeforeEach [OK]
    | '-- AfterEach [OK]
    :

--TestTemplateInvocationContext defines a method calledgetAdditionalExtensions () --This method returns the extension to use in that context with List <Extension> --This method is also the default method, and the default implementation returns an empty List <Extension>. --In the above implementation example, BeforeEachCallback is set in the first context and ʻAfterEachCallbakis set in the second context. --By applyingParameterResolver`, you can pass different parameters for each context and execute the same test method.

Register the extension procedurally

MyRegisterExtension


package sample.junit5.extension;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class MyRegisterExtension implements BeforeEachCallback, BeforeAllCallback {
    private final String name;

    public MyRegisterExtension(String name) {
        this.name = name;
    }

    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        System.out.println("[" + this.name + "] beforeAll()");
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        System.out.println("[" + this.name + "] beforeEach()");
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import sample.junit5.extension.MyRegisterExtension;

class JUnit5Test {
    @RegisterExtension
    static MyRegisterExtension classField = new MyRegisterExtension("classField");
    @RegisterExtension
    MyRegisterExtension instanceField = new MyRegisterExtension("instanceField");

    @BeforeAll
    static void beforeAll() {
        System.out.println("beforeAll()");
    }
    
    @BeforeEach
    void beforeEach() {
        System.out.println("beforeEach()");
    }
    
    @Test
    void test1() {
        System.out.println("test1()");
    }
}

Execution result


[classField] beforeAll()
beforeAll()
[classField] beforeEach()
[instanceField] beforeEach()
beforeEach()
test1()

--In the case of the method using @ExtendWith, the adjustment of the extension class is basically static. --The instance of the class that implements the extension is created behind the scenes by Jupiter. --For this reason, it is basically impossible to make fine adjustments to instances of extension classes. --On the other hand, if you use @RegisterExtension, you can dynamically adjust the extension class. Will be able to specify --Declare the instance of the extension class you want to use as a field (static or instance) of the test class you want to use the extension. --By annotating this field with @RegisterExtension, you can register the instance set in that field as an extension. --Since the instance setting in the field can be described by any program [^ 7], you can use the freely adjusted instance. --Any extension is available when using fields declared with static --Class-level extensions like BeforeAllCallback and instance-level extensions like TestInstancePostProcessor are not available when using instance fields --Ignored even if implemented --Method-level extensions like BeforeEachCallback are available

[^ 7]: You can simply generate it with a constructor, prepare a mechanism like a builder, or make it a factory method.

Register automatically using ServiceLoader

Folder structure


`-src/test/
  |-java/
  | `-sample/junit5/
  |   `-JUnit5Test.java
  `-resources/
    |-junit-platform.properties
    `-META-INF/services/
      `-org.junit.jupiter.api.extension.Extension

text:org.junit.jupiter.api.extension.Extension


sample.junit5.extension.MyServiceLoaderExtension

junit-platform.properties


junit.jupiter.extensions.autodetection.enabled=true

MyServiceLoaderExtension


package sample.junit5.extension;

import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class MyServiceLoaderExtension implements BeforeEachCallback {
    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        System.out.println("MyServiceLoaderExtension.beforeEach()");
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.Test;

class JUnit5Test {
    
    @Test
    void test1() {
        System.out.println("test1()");
    }
}

Execution result


MyServiceLoaderExtension.beforeEach()
test1()

--Extensions can also be registered automatically using the ServiceLoader mechanism --Create a / META-INF / services / folder under the classpath and create a file named ʻorg.junit.jupiter.api.extension.Extensionin it. --In the file, describe the fully qualified name of the extension class to be registered. --When registering multiple items, list them separated by line breaks. --To enable auto-registration using ServiceLoader, you must specifytrue for the configuration parameter junit.jupiter.extensions.autodetection.enabled. --There is a way to specify it in the configuration file (junit-platform.properties`), but you can also specify it in the system properties.

Share data between Extensions

Consider how to refer to the information recorded when one Extension is executed when another Extension is executed.

For example, an image that records the start time of a test method with BeforeEachCallback and outputs the execution time from the difference between the current time and the start time at ʻAfterEachCallback`.

If you're building your extension in a single class, you'll have a quick way to use instance variables.

MyStopwatch


package sample.junit5.extension;

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class MyStopwatch implements BeforeEachCallback, AfterEachCallback {
    
    private long startTime;

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        this.startTime = System.currentTimeMillis();
    }

    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        String displayName = context.getDisplayName();
        long endTime = System.currentTimeMillis();
        long time = endTime - this.startTime;
        System.out.println("[" + displayName + "] time=" + time + " (startTime=" + this.startTime + ", endTime=" + endTime + ")");
    }
}

Actually apply it to the following test and execute it.

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyStopwatch;

import java.util.concurrent.TimeUnit;

@ExtendWith(MyStopwatch.class)
class JUnit5Test {

    @Test
    void test1() throws Exception {
        TimeUnit.MILLISECONDS.sleep(200);
    }
    
    @Test
    void test2() throws Exception {
        TimeUnit.MILLISECONDS.sleep(400);
    }

    @Test
    void test3() throws Exception {
        TimeUnit.MILLISECONDS.sleep(600);
    }
}

Execution result


[test1()] time=211 (startTime=1577191073870, endTime=1577191074081)
[test2()] time=400 (startTime=1577191074126, endTime=1577191074526)
[test3()] time=602 (startTime=1577191074529, endTime=1577191075131)

It worked like that.

However, there are ** problems with this implementation **.

If you try to run this test in parallel, the problem becomes apparent.

junit-platform.properties


junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=2
junit.jupiter.execution.parallel.mode.default=concurrent

Execution result


[test1()] time=214 (startTime=1577191442987, endTime=1577191443201)
[test3()] time=363 (startTime=1577191443234, endTime=1577191443597)
[test2()] time=402 (startTime=1577191443234, endTime=1577191443636)

The execution time of test3 () is about 300ms (actually, this value is impossible because it sleeps for 600ms).

If you look closely, you can see that startTime oftest3 ()andtest2 ()have the same value. This means that the startTime used to timetest3 ()has become the start time oftest2 ().

The cause of this problem is that the startTime is shared by the instance variable of MyStopwatch. MyStopwatch is using the same instance while the JUnit5Test test is running. In other words, the MyStopwatch used when each test method is executed is all the same instance.

When test3 () is executed, beforeEach () records the start time in startTime. However, immediately after that, test2 () is executed in parallel, and the value of startTime is overwritten by the start time oftest2 (). As a result, the above-mentioned problems have occurred.

In this way, using instance variables of the implementation class for data sharing between Extensions can cause unexpected problems depending on how the test is executed. (There may be other patterns that cause problems, but for the time being, the only case I can think of is this parallel execution case.)

Use Store

I don't know if it's meant to solve this problem, but Store allows you to implement data sharing so that it doesn't cause problems when run in parallel.

MyStopwatch


package sample.junit5.extension;

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class MyStopwatch implements BeforeEachCallback, AfterEachCallback {
    
    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("stopwatch"));
        store.put("startTime", System.currentTimeMillis());
    }

    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("stopwatch"));
        long startTime = store.get("startTime", long.class);

        long endTime = System.currentTimeMillis();
        long time = endTime - startTime;

        String displayName = context.getDisplayName();
        System.out.println("[" + displayName + "] time=" + time + " (startTime=" + startTime + ", endTime=" + endTime + ")");
    }
}

Execution result


[test1()] time=213 (startTime=1577193397891, endTime=1577193398104)
[test3()] time=609 (startTime=1577193397891, endTime=1577193398500)
[test2()] time=401 (startTime=1577193398142, endTime=1577193398543)

The time of test3 () is about 600ms and it works well. (StartTime is the same astest1 ()becausetest1 ()andtest3 ()are started at the same time with 2 parallels, so there is no problem)

This implementation uses a mechanism called Store. Store is a container of data prepared for each ʻExtensionContext`, and arbitrary data can be saved in Key-Value format.

ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("stopwatch"));
store.put("startTime", System.currentTimeMillis());

...

long startTime = store.get("startTime", long.class);

An instance of Store is ʻExtensionContext`'s [getStore (Namespace)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.html#getStore(org) It can be obtained by the .junit.jupiter.api.extension.ExtensionContext.Namespace)) method. Specify Namespace as an argument.

As an image, multiple Stores are stored in ʻExtensionContext, and it feels like Namespacespecifies whichStoreto get. (Actually,Namespace is only used as part of the key, and the entity of Storeis implemented by a singleMap`, but I think this is fine as an image)

junit5.jpg

By separating Store in Namespace in this way, even if there are multiple extensions that use the same key, Store can be separated and data can be shared. By the way, if you want to share data with all extensions, [Namespace.GLOBAL](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.Namespace You can also use a predefined constant called .html # GLOBAL).

[Create (Object ...)](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext.Namespace.html#create to create Namespace (java.lang.Object ...)) Use the method. Any object can be specified as an argument, but it must be an object that can be compared and verified with the ʻequals () `method.

How to find the store life cycle and keys

The life cycle of the Store matches the source ʻExtensionContext`.

MyExtension


package sample.junit5.extension;

import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class MyExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback {

    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        System.out.println("[beforeAll]");
        this.printStoreValues(context);
        
        ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("foo"));
        store.put("hoge", "INITIAL VALUE");
    }
    
    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        System.out.println("[beforeEach@" + context.getDisplayName() + "]");
        this.printStoreValues(context);

        ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("foo"));
        store.put("hoge", context.getDisplayName());
    }

    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        System.out.println("[afterEach@" + context.getDisplayName() + "]");
        this.printStoreValues(context);
    }

    @Override
    public void afterAll(ExtensionContext context) throws Exception {
        System.out.println("[afterAll]");
        this.printStoreValues(context);
    }
    
    private void printStoreValues(ExtensionContext context) {
        ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create("foo"));
        System.out.println("  hoge=" + store.get("hoge"));
        System.out.println("  context.class=" + context.getClass().getCanonicalName());
    }
}

--In beforeAll () and beforeEach (), first output the information of Store and then set the value of hoge. --And, ʻafterEach () and ʻafterAll () output the information of Store as it is. --Each, the class name of ʻExtensionContext` is also output.

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyExtension;

@ExtendWith(MyExtension.class)
class JUnit5Test {

    @Test
    void test1() throws Exception {}
    
    @Test
    void test2() throws Exception {}
}

Execution result


[beforeAll]
  hoge=null
  context.class=org.junit.jupiter.engine.descriptor.ClassExtensionContext

[beforeEach@test1()]
  hoge=INITIAL VALUE
  context.class=org.junit.jupiter.engine.descriptor.MethodExtensionContext
[afterEach@test1()]
  hoge=test1()
  context.class=org.junit.jupiter.engine.descriptor.MethodExtensionContext

[beforeEach@test2()]
  hoge=INITIAL VALUE
  context.class=org.junit.jupiter.engine.descriptor.MethodExtensionContext
[afterEach@test2()]
  hoge=test2()
  context.class=org.junit.jupiter.engine.descriptor.MethodExtensionContext

[afterAll]
  hoge=INITIAL VALUE
  context.class=org.junit.jupiter.engine.descriptor.ClassExtensionContext

--In class-level extensions like BeforeAllCallback and ʻAfterAllCallback, ClassExtensionContext is passed as ʻExtensionContext. --On the other hand, method-level extensions like BeforeEachCallback and ʻAfterEachCallback pass MethodExtensionContext. --In this way, ʻExtensionContext is passed an implementation instance that matches the level at which the extension is running. --These ʻExtensionContexthave a parent-child relationship, and the parent ofMethodExtensionContext is ClassExtensionContext. --If you follow the parent's context, you will end up with JupiterEngineExtensionContext. --The parent context is ʻExtensionContext's [getParent ()](https://junit.org/junit5/docs/5.0.2/api/org/junit/jupiter/api/extension/ExtensionContext.html#getParent- -) Can be obtained by method --JupiterEngineExtensionContext is the context of the most parent (root) --The root context is ʻExtensionContext [getRoot ()](https://junit.org/junit5/docs/5.0.2/api/org/junit/jupiter/api/extension/ExtensionContext.html#getRoot- -) Can be obtained by method --In addition, there are other implementation classes of ʻExtensionContext such as TestTemplateExtensionContext and DynamicExtensionContext, but they are omitted here. --Store is kept for each instance of these contexts --MethodExtensionContext is generated for each test method --That is, the MethodExtensionContext oftest1 ()andtest2 ()passes different instances. ――Therefore, Store is also different. --As a result, the information saved in Store bytest1 ()cannot be referenced bytest2 (). --Thanks to this, MyStopwatch was able to avoid problems during parallel execution by using Store. --BeforeEachCallback and ʻAfterEachCallbackare method-level extensions --Since the context is different for each test method to be executed,Storeis also different. ――For this reason, even if they are executed in parallel, the data does not conflict and it can operate normally. --Information stored in the parent contextStore can also be referenced from the child context Store. -And it can be overwritten in the child context --However, the overwritten information returns to the original value set in the parent context when the scope of the child context ends and it moves to another context. --The value set in test1 ()returns to the original" INITIAL VALUE " at the stage of beforeEach () and ʻafterAll () of test2 (). --This behavior is achieved by recursively searching the parent Store if the specified key information does not exist in Store. --In other words, if it does not exist in the Store of the MethodExtensionContext, it will search for the Store of the parent ClassExtensionContext by tracing to the root Store if it is not there. --This makes it possible to refer to the value set in the parent context from test1 (). --However, even if you set the value to Store in the child context, the Store in the parent context remains the same, so when the child context ends, the information in the parent context is restored. --If you draw the relationship between ʻExtensionContext and Store` in a diagram, it looks like the one below.

junit5.jpg

Perform processing at the end of the life cycle

MyCloseableResource


package sample.junit5.extension;

import org.junit.jupiter.api.extension.ExtensionContext;

public class MyCloseableResource implements ExtensionContext.Store.CloseableResource {
    private final String name;

    public MyCloseableResource(String name) {
        this.name = name;
    }

    @Override
    public void close() throws Throwable {
        System.out.println("  Close Resource > " + this.name);
    }
}

MyExtension


package sample.junit5.extension;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class MyExtension implements BeforeEachCallback, BeforeAllCallback {

    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        MyCloseableResource resource = new MyCloseableResource("BeforeAll");
        context.getStore(ExtensionContext.Namespace.GLOBAL).put("foo", resource);
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        MyCloseableResource resource = new MyCloseableResource("BeforeEach(" + context.getDisplayName() + ")");
        context.getStore(ExtensionContext.Namespace.GLOBAL).put("foo", resource);
    }
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyExtension;

@ExtendWith(MyExtension.class)
class JUnit5Test {

    @Test
    void test1() throws Exception {
        System.out.println("test1()");
    }
    
    @Test
    void test2() throws Exception {
        System.out.println("test2()");
    }
    
    @AfterAll
    static void afterAll() {
        System.out.println("afterAll()");
    }
}

Execution result


test1()
  Close Resource > BeforeEach(test1())
test2()
  Close Resource > BeforeEach(test2())
afterAll()
  Close Resource > BeforeAll

--An instance that implements CloseableResource is saved in Store If so, at the end of the Store lifecycle [close ()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/extension/ExtensionContext. Store.CloseableResource.html # close ()) method is called automatically

Meta annotation

MyParameterResolver


package sample.junit5.extension;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;

public class MyParameterResolver implements ParameterResolver {
    
    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
        return parameterContext.getParameter().getType().equals(String.class);
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
        return extensionContext.getRequiredTestMethod().getName();
    }
}

MyParameterResolverExtension


package sample.junit5.extension;

import org.junit.jupiter.api.extension.ExtendWith;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ExtendWith(MyParameterResolver.class)
public @interface MyParameterResolverExtension {}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.Test;
import sample.junit5.extension.MyParameterResolverExtension;

@MyParameterResolverExtension
class JUnit5Test {
    
    @Test
    void test1(String testMethodName) {
        System.out.println("[test1] testMethodName=" + testMethodName);
    }

    @Test
    void test2(String testMethodName) {
        System.out.println("[test2] testMethodName=" + testMethodName);
    }
}

Execution result


[test1] testMethodName=test1
[test2] testMethodName=test2

--JUnit Jupiter supports the meta-annotation mechanism --In other words, you can define another self-made annotation that is a collection of arbitrary annotations. --If you use the same combination of annotations in multiple places, you can combine them into a single self-made annotation to improve reusability. --Rather than directly specifying the Class object with @ExtendWith for the extension, there may be an advantage that you can separate it from the concrete implementation by inserting your own annotation.

Setting parameters

To adjust test behavior such as junit.jupiter.testinstance.lifecycle.default to change the default lifecycle of a test instance and junit.jupiter.execution.parallel.enabled to enable parallel execution. The parameters are called ** configuration parameters **.

The following three methods are available for specifying the setting parameters.

  1. Specify by the method prepared for each Launcher
  2. Specify in JVM system properties
  3. Specify in junit-platform.properties

The method provided for each first Launcher is specified by the --config option when using ConsoleLauncher, for example. Gradle doesn't seem to currently support specifying this way (instead it uses system properties or junit-platform.properties). For Maven, specify with the configurationParameters property.

The second JVM system property is specified as it is in the system property (the specification method differs depending on each Launcher).

The third is enabled by placing a property file called junit-platform.properties at the root of the classpath.

If the same key configuration parameters are specified in these different ways, the value defined above takes precedence. That is, the value provided for each Launcher has the highest priority, and the value defined in junit-platform.properties has the lowest priority.

Actually, try it with ConsoleLauncher.

MyExtension


package sample.junit5.extension;

import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class MyExtension implements BeforeEachCallback {
    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        this.printConfigurationParameter(context, "hoge");
        this.printConfigurationParameter(context, "fuga");
        this.printConfigurationParameter(context, "piyo");
    }
    
    private void printConfigurationParameter(ExtensionContext context, String key) {
        System.out.println("key=" + key + ", value=" + context.getConfigurationParameter(key).orElse("<empty>"));
    }
}

--The value of the configuration parameter can be referenced by getConfigurationParameter () of ʻExtensionContext. --Here, I try to output the values of the three setting parameters hoge, fuga, and piyo`.

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.junit5.extension.MyExtension;

@ExtendWith(MyExtension.class)
class JUnit5Test {
    
    @Test
    void test1() {
        System.out.println("test1()");
    }
}

junit-platform.properties


hoge=HOGE@properties
fuga=FUGA@properties
piyo=PIYO@properties

--hoge, fuga, piyo defines everything

> java -Dfuga=FUGA@SystemProperty ^
       -Dpiyo=PIYO@SystemProperty ^
       -jar junit-platform-console-standalone-1.5.2.jar ^
       --config piyo=PIYO@ConfigOption ^
...

--fuga and piyo are specified in the JVM system properties --Only piyo is also specified in the --config option.

Execution result


key=hoge, value=HOGE@properties
key=fuga, value=FUGA@SystemProperty
key=piyo, value=PIYO@ConfigOption
test1()

The table below shows the relationship between each key and the value specified for each setting method. (The value with * is the value finally adopted)

Key junit-platform.properties JVM system properties --config
hoge HOGE@properties *
fuga FUGA@properties FUGA@SystemProperty *
piyo PIYO@properties PIYO@SystemProperty PIYO@ConfigOption *

You can see that the value specified by --config has priority and the value specified by junit-platform.properties has lower priority.

Assertion

JUnit5 provides a class for assertions called Assertions.

Here you will find assertEquals () are provided. Also, assertTimeout () and [assertAll ()](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assertions.html#assertAll (org.junit.jupiter) There are also some useful methods like .api.function.Executable ...)).

However, the impression is that only the minimum necessary items are prepared.

JUnit5 allows you to use any third-party assertion library. In other words, if you add AssertJ or Hamcrest to the dependent libraries, you can add it to JUnit4. Can be used normally.

The following is an example of adding AssertJ to a dependency.

build.gradle


dependencies {
    ...
    testImplementation "org.assertj:assertj-core:3.11.1"
}

JUnit5Test


package sample.junit5;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;

class JUnit5Test {

    @Test
    void test() throws Exception {
        assertThat(10).isEqualTo(8);
    }
}

Execution result


...
.
'-- JUnit Jupiter [OK]
  '-- JUnit5Test [OK]
    '-- test() [X]
          Expecting:
           <10>
          to be equal to:
           <8>
          but was not.
...

In reality, I think I'll be using these third-party assertions, so I'll omit the use of standard assertions.

reference

-JUnit 5 User Guide

Recommended Posts

JUnit5 usage memo
JavaParser usage memo
WatchService usage memo
Spring Shell usage memo
Spring Security usage memo CSRF
Spring Security usage memo Run-As
Introduction to JUnit (study memo)
Spring Security Usage memo Method security
Spring Security usage memo Remember-Me
Dependency Management Plugin Usage memo
junit
Spring Security usage memo test
Spring Security usage memo Authentication / authorization
JCA (Java Cryptography Architecture) Usage Memo
Spring Security usage memo response header
Spring Security usage memo session management
Spring Security usage memo Basic / mechanism
Integer memo
docker memo
Spring Security Usage Memo Domain Object Security (ACL)
JUnit 4 notes
Lombok memo
JUnit story
Dockerfile memo
Java memo
AWS memo
[Personal] JUnit5 memorandum memo (work in progress)
JUnit memorandum
Ruby memo
irb usage
Memo Stream