In JUnit 5, we can create dynamic tests that are generated at runtime by factory methods. These methods are annotated with the annotation TestFactory.

The method annotated with @TestFactory must return a Stream, Collection, Iterable, or Iterator of DynamicNode objects. DynamicNode is abstract. It has two subclasses, DynamicContainer is the container of other DynamicNode objects; DynamicTest is a generated test case. DynamicTest is created from a display name and an Executable object. Executable is a functional interface that contains only one method void execute(). DynamicContainer is created from a display name and a collection of DynamicNodes.

The code below shows a simple test factory. DynamicTest.dynamicTest(String displayName, Executable executable) is the static method to create DynamicTest objects.

@TestFactory
@DisplayName("simple")
List<DynamicTest> simpleDynamicTests() {
  return Lists.newArrayList(
      DynamicTest.dynamicTest("test1", () -> assertTrue(true)),
      DynamicTest.dynamicTest("test2", () -> assertEquals(3, 1 + 2))
  );
}

DynamicTest has another static method stream(Iterator<T> inputGenerator, Function<? super T, String> displayNameGenerator, ThrowingConsumer<? super T> testExecutor) to create a Stream of DynamicTests. The method stream requires three parameters. The first parameter is an Iterator<T> object that generates the input values. Each input value will be mapped to a generated test case. The second parameter is a Function<? super T, String> object that generates display names based on the input value. The third parameter is a ThrowingConsumer<? super T> object that contains the testing logic.

In the code below, the input values are integers from 1 to 10. The display names are generated using format Test - %s. The actual test is to verify the numbers are greater than 0.

@TestFactory
@DisplayName("test generators")
Stream<DynamicTest> testGenerators() {
  return DynamicTest.stream(
      IntStream.range(1, 10).iterator(),
      num -> String.format("Test - %s", num),
      num -> assertTrue(num > 0)
  );
}

The figure below shows the result of running this test factory in IntelliJ IDEA.

Dynamic test generator

We can also use static method DynamicContainer.dynamicContainer(String displayName, Stream<? extends DynamicNode> dynamicNodes) to create DynamicContainers. In the code below, we create nested tests using DynamicContainers and DynamicTests.

@TestFactory
@DisplayName("nested")
Stream<DynamicNode> nestedTests() {
  return Stream.of(DynamicContainer.dynamicContainer("nested",
      Stream.of(
          DynamicContainer.dynamicContainer("t1",
              Stream.of(
                  DynamicTest.dynamicTest("1", () -> assertTrue(true)),
                  DynamicTest.dynamicTest("2", () -> assertTrue(true))
              )),
          DynamicTest.dynamicTest("t2", () -> assertTrue(true))
      )));
}

Check out the book JUnit 5 Cookbook for more new features in JUnit 5.