Close

JUnit 5 - Dynamic Tests with NamedExecutable

[Last Updated: Dec 30, 2025]

The NamedExecutable interface (introduced in 5.11) combines Executable and Named. This allows @TestFactory methods to return a stream of objects that carry both their execution logic and their display names, reducing the need for manual wrapping with DynamicTest.dynamicTest().

Java source and doc

Definition of NamedExecutable

Version: 6.0.0
 package org.junit.jupiter.api;
 @FunctionalInterface
 @API(status = MAINTAINED, since = "5.13.3")
 public interface NamedExecutable extends Named<Executable>, Executable {
     @Override
     default String getName() {
         ...
     }
     @Override
     default Executable getPayload() {
         ...
     }
 }

Example

In this example, we use a Java record to implement NamedExecutable. This is a concise way to define data-driven scenarios where the record's state provides the test identity via getName() and the assertion logic via execute().

package com.logicbig.example;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.NamedExecutable;
import org.junit.jupiter.api.TestFactory;
import java.util.stream.Stream;

public class NamedExecutableTest {

    @TestFactory
    Stream<DynamicTest> dynamicTests() {
        return DynamicTest.stream(Stream.of(
                new MathTask(5, 5, 10),
                new MathTask(20, 30, 50),
                new MathTask(100, 1, 101)
        ));
    }

    record MathTask(int n1, int n2, int expected) implements NamedExecutable {

        @Override
        public void execute() {
            System.out.println("Checking " + n1 + " + " + n2);
            Assertions.assertEquals(expected, n1 + n2);
        }

        @Override
        public String getName() {
            return "Sum Test: " + n1 + " + " + n2 + " = " + expected;
        }
    }
}

Output

$ mvn test -Dtest=NamedExecutableTest
[INFO] Scanning for projects...
[INFO]
[INFO] -----------< com.logicbig.example:junit-5-named-executable >------------
[INFO] Building junit-5-named-executable 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ junit-5-named-executable ---
[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\dynamic-tests\junit-5-named-executable\src\main\resources
[INFO]
[INFO] --- compiler:3.13.0:compile (default-compile) @ junit-5-named-executable ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ junit-5-named-executable ---
[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\dynamic-tests\junit-5-named-executable\src\test\resources
[INFO]
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ junit-5-named-executable ---
[INFO] Recompiling the module because of changed source code.
[WARNING] File encoding has not been set, using platform encoding windows-1252, i.e. build is platform dependent!
[INFO] Compiling 1 source file with javac [debug target 17] to target\test-classes
[INFO]
[INFO] --- surefire:3.5.0:test (default-test) @ junit-5-named-executable ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.948 s
[INFO] Finished at: 2025-12-29T00:58:49+08:00
[INFO] ------------------------------------------------------------------------

(1) [WARNING] @TestFactory method 'java.util.stream.Stream<org.junit.jupiter.api.NamedExecutable> com.logicbig.example.NamedExecutableTest.dynamicTests()' must return a single org.junit.jupiter.api.DynamicNode or a Stream, Collection, Iterable, Iterator, Iterator provider, or array of org.junit.jupiter.api.DynamicNode. It will not be executed.
Source: MethodSource [className = 'com.logicbig.example.NamedExecutableTest', methodName = 'dynamicTests', methodParameterTypes = '']
at com.logicbig.example.NamedExecutableTest.dynamicTests(SourceFile:0)

Output Analysis

The above output confirms that the JUnit 5 engine successfully discovered the NamedExecutable instances within the stream. Each test was executed with the custom name provided by the record's getName() method, demonstrating how this interface simplifies the creation of dynamic tests by bundling data, naming, and logic into a single cohesive unit.

Example Project

Dependencies and Technologies Used:

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

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

    • 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 - Dynamic Tests with NamedExecutable Select All Download
  • junit-5-named-executable
    • src
      • test
        • java
          • com
            • logicbig
              • example
                • NamedExecutableTest.java

    See Also