Close

JUnit 5 - JUnit 5 - Hierarchical Dynamic Tests

[Last Updated: Dec 30, 2025]

While standard dynamic tests provide a flat list of results, DynamicContainer allows for the creation of hierarchical dynamic tests. This is particularly useful when you need to group related dynamic tests into nested structures, similar to how @Nested works for static tests.

A @TestFactory method can return a Stream of DynamicNode, which is the common supertype for both DynamicTest (individual test cases) and DynamicContainer (groups of nodes).

java.lang.ObjectObjectorg.junit.jupiter.api.DynamicNodeDynamicNodeorg.junit.jupiter.api.DynamicTestDynamicTestorg.junit.jupiter.api.DynamicContainerDynamicContainerLogicBig

In JUnit Jupiter, DynamicTest represents a single executable test case generated at runtime. A DynamicContainer is a group of dynamic nodes (tests or nested containers) used to organize dynamic tests hierarchically, like a test suite. DynamicNode is the common abstract superclass for both, providing the base structure for the dynamic test tree. Use DynamicTest for individual tests, DynamicContainer for grouping, and refer to DynamicNode when handling either type generically.

Useful factory methods of DynamicContainer

public static DynamicContainer dynamicContainer(String displayName, 
                                                Iterable<? extends DynamicNode> dynamicNodes) 
public static DynamicContainer dynamicContainer(String displayName, 
                                                Stream<? extends DynamicNode> dynamicNodes) 

Examples

The following example creates a multi-level test hierarchy. We use dynamicContainer() to group tests and dynamicTest() for the actual assertions.

package com.logicbig.example;

import org.junit.jupiter.api.DynamicContainer;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

public class HierarchicalDynamicNodeTest {

    @TestFactory
    Stream<DynamicContainer> dynamicNodeHierarchy() {
        return Stream
                .of("Node A", "Node B")
                .map(HierarchicalDynamicNodeTest::getMainDynamicContainer);
    }

    private static DynamicContainer getMainDynamicContainer(String input) {
        return dynamicContainer("Container for " + input,
                                createDynamicNodeStream(input));
    }

    private static Stream<DynamicNode> createDynamicNodeStream(String input) {
        return Stream.of(
                dynamicTest("test not null",
                            () -> assertNotNull(input)),
                dynamicContainer("Sub-container",
                                 createAnotherDynamicTest(input))
        );
    }

    private static Stream<DynamicTest> createAnotherDynamicTest(String input) {
        return Stream.of(
                dynamicTest("test length",
                            () -> assertTrue(!input.isEmpty()))
        );
    }
}

Output

$ mvn test -Dtest=HierarchicalDynamicNodeTest
[INFO] Scanning for projects...
[INFO]
[INFO] ------< com.logicbig.example:junit-5-hierarchical-dynamic-nodes >-------
[INFO] Building junit-5-hierarchical-dynamic-nodes 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ junit-5-hierarchical-dynamic-nodes ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\junit-5\junit-5-dynamic-tests\junit-5-hierarchical-dynamic-nodes\src\main\resources
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ junit-5-hierarchical-dynamic-nodes ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ junit-5-hierarchical-dynamic-nodes ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\junit-5\junit-5-dynamic-tests\junit-5-hierarchical-dynamic-nodes\src\test\resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ junit-5-hierarchical-dynamic-nodes ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- surefire:3.5.0:test (default-test) @ junit-5-hierarchical-dynamic-nodes ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] +--com.logicbig.example.HierarchicalDynamicNodeTest - 0.104 ss
[INFO] | +-- [OK] Container for Node A dynamicNodeHierarchy() test not null - 0.015 ss
[INFO] | +-- [OK] Sub-container Container for Node A dynamicNodeHierarchy() test length - 0.001 ss
[INFO] | +-- [OK] Container for Node B dynamicNodeHierarchy() test not null - 0.001 ss
[INFO] | '-- [OK] Sub-container Container for Node B dynamicNodeHierarchy() test length - 0.002 ss
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.062 s
[INFO] Finished at: 2025-12-28T21:57:56+08:00
[INFO] ------------------------------------------------------------------------

Using DynamicContainer

In this specific case, the factory method returns a Stream<DynamicContainer>. This is useful when every top-level element is guaranteed to be a group of tests.

package com.logicbig.example;

import org.junit.jupiter.api.DynamicContainer;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import java.util.Arrays;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

public class DynamicContainerTest {

    @TestFactory
    Stream<DynamicContainer> dynamicContainerExample() {
        return Stream.
                of("math", "logic")
                .map(DynamicContainerTest::createMainDynamicContainer);
    }

    private static DynamicContainer createMainDynamicContainer(String topic) {
        return dynamicContainer("Topic: " + topic,
                                Arrays.asList(
                                        getDynamicTest(topic),
                                        getAnotherDynamicTest(topic)
                                ));
    }

    private static DynamicTest getAnotherDynamicTest(String topic) {
        return dynamicTest(topic + " test 2",
                           () -> assertEquals(4, 2 + 2));
    }

    private static DynamicTest getDynamicTest(String topic) {
        return dynamicTest(topic + " test 1",
                           () -> assertTrue(true));
    }
}

Output

$ mvn test -Dtest=DynamicContainerTest
[INFO] Scanning for projects...
[INFO]
[INFO] ------< com.logicbig.example:junit-5-hierarchical-dynamic-nodes >-------
[INFO] Building junit-5-hierarchical-dynamic-nodes 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ junit-5-hierarchical-dynamic-nodes ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\junit-5\junit-5-dynamic-tests\junit-5-hierarchical-dynamic-nodes\src\main\resources
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ junit-5-hierarchical-dynamic-nodes ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ junit-5-hierarchical-dynamic-nodes ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\junit-5\junit-5-dynamic-tests\junit-5-hierarchical-dynamic-nodes\src\test\resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ junit-5-hierarchical-dynamic-nodes ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- surefire:3.5.0:test (default-test) @ junit-5-hierarchical-dynamic-nodes ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] +--com.logicbig.example.DynamicContainerTest - 0.189 ss
[INFO] | +-- [OK] Topic: math dynamicContainerExample() math test 1 - 0.034 ss
[INFO] | +-- [OK] Topic: math dynamicContainerExample() math test 2 - 0.003 ss
[INFO] | +-- [OK] Topic: logic dynamicContainerExample() logic test 1 - 0.002 ss
[INFO] | '-- [OK] Topic: logic dynamicContainerExample() logic test 2 - 0.003 ss
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.411 s
[INFO] Finished at: 2025-12-28T16:18:57+08:00
[INFO] ------------------------------------------------------------------------

Output Analysis

The above output confirms that JUnit 5 successfully resolved the nested structures provided by the @TestFactory methods. By returning DynamicNode and DynamicContainer, we can see in the execution report how tests are logically grouped into folders or nodes, allowing for a cleaner representation of complex, runtime-generated test scenarios compared to a flat list of dynamic tests.

Example Project

Dependencies and Technologies Used:

  • junit-jupiter-engine 6.0.1 (Module "junit-jupiter-engine" of JUnit)
     Version Compatibility: 5.0.0 - 6.0.1Version List
    ×

    Version compatibilities of junit-jupiter-engine with this example:

    • 5.0.0
    • 5.0.1
    • 5.0.2
    • 5.0.3
    • 5.1.0
    • 5.1.1
    • 5.2.0
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.4.0
    • 5.4.1
    • 5.4.2
    • 5.5.0
    • 5.5.1
    • 5.5.2
    • 5.6.0
    • 5.6.1
    • 5.6.2
    • 5.6.3
    • 5.7.0
    • 5.7.1
    • 5.7.2
    • 5.8.0
    • 5.8.1
    • 5.8.2
    • 5.9.0
    • 5.9.1
    • 5.9.2
    • 5.9.3
    • 5.10.0
    • 5.10.1
    • 5.10.2
    • 5.10.3
    • 5.10.4
    • 5.10.5
    • 5.11.0
    • 5.11.1
    • 5.11.2
    • 5.11.3
    • 5.11.4
    • 5.12.0
    • 5.12.1
    • 5.12.2
    • 5.13.0
    • 5.13.1
    • 5.13.2
    • 5.13.3
    • 5.13.4
    • 5.14.0
    • 5.14.1
    • 6.0.0
    • 6.0.1

    Versions in green have been tested.

  • JDK 25
  • Maven 3.9.11

JUnit 5 - Hierarchical Dynamic Tests Select All Download
  • junit-5-hierarchical-dynamic-nodes
    • src
      • test
        • java
          • com
            • logicbig
              • example
                • HierarchicalDynamicNodeTest.java

    See Also