JUnit Platform Engine API - Core engine API for implementing custom test engines in the JUnit Platform ecosystem
—
Hierarchical test descriptor system for representing discovered tests and containers with metadata, sources, and relationships. Test descriptors form a tree structure that represents the organization of tests within an engine.
Core interface representing a single test or container in the test hierarchy, providing metadata, child management, and source information.
/**
* Mutable descriptor for a test or container within the test hierarchy.
*/
public interface TestDescriptor {
/**
* Get the unique identifier for this descriptor.
* @return unique ID within the engine's namespace
*/
UniqueId getUniqueId();
/**
* Get the display name for this descriptor.
* @return human-readable name for display purposes
*/
String getDisplayName();
/**
* Get the set of tags associated with this descriptor.
* @return unmodifiable set of test tags
*/
Set<TestTag> getTags();
/**
* Get the source location of this test or container.
* @return optional test source (class, method, file, etc.)
*/
Optional<TestSource> getSource();
/**
* Get the parent descriptor of this descriptor.
* @return optional parent descriptor
*/
Optional<TestDescriptor> getParent();
/**
* Set the parent descriptor of this descriptor.
* @param parent the parent descriptor
*/
void setParent(TestDescriptor parent);
/**
* Get all direct child descriptors.
* @return unmodifiable set of child descriptors
*/
Set<? extends TestDescriptor> getChildren();
/**
* Add a child descriptor to this descriptor.
* @param child the child descriptor to add
*/
void addChild(TestDescriptor child);
/**
* Remove a child descriptor from this descriptor.
* @param child the child descriptor to remove
*/
void removeChild(TestDescriptor child);
/**
* Remove this descriptor from its parent.
*/
void removeFromHierarchy();
/**
* Get the type of this descriptor.
* @return descriptor type (CONTAINER, TEST, or CONTAINER_AND_TEST)
*/
Type getType();
/**
* Find the first descendant matching the given predicate.
* @param predicate the matching predicate
* @return optional matching descendant
*/
Optional<? extends TestDescriptor> findByUniqueId(UniqueId uniqueId);
/**
* Accept a visitor for traversing the descriptor hierarchy.
* @param visitor the visitor to accept
*/
void accept(Visitor visitor);
/**
* Check if this descriptor contains any executable tests.
* @param testDescriptor the descriptor to check
* @return true if contains tests, false otherwise
*/
static boolean containsTests(TestDescriptor testDescriptor);
/**
* Type of test descriptor.
*/
enum Type {
/** Contains other tests or containers but is not executable itself */
CONTAINER,
/** Executable test that does not contain other tests */
TEST,
/** Both container and executable test */
CONTAINER_AND_TEST
}
/**
* Visitor interface for traversing test descriptor hierarchies.
*/
interface Visitor {
/**
* Visit a test descriptor.
* @param descriptor the descriptor to visit
* @param remove function to remove the descriptor from its parent
*/
void visit(TestDescriptor descriptor, Runnable remove);
}
}Usage Example:
// Create hierarchical test structure
TestDescriptor engineDescriptor = new EngineDescriptor(
UniqueId.forEngine("my-engine"), "My Test Engine"
);
// Add class container
UniqueId classId = engineDescriptor.getUniqueId().append("class", "MyTestClass");
TestDescriptor classDescriptor = new MyClassDescriptor(classId, MyTestClass.class);
engineDescriptor.addChild(classDescriptor);
// Add test methods
UniqueId methodId = classId.append("method", "testSomething");
TestDescriptor methodDescriptor = new MyMethodDescriptor(methodId, testMethod);
classDescriptor.addChild(methodDescriptor);
// Navigate hierarchy
Optional<TestDescriptor> found = engineDescriptor.findByUniqueId(methodId);
Set<? extends TestDescriptor> children = classDescriptor.getChildren();Abstract base implementation providing common functionality for test descriptors, including hierarchy management and visitor support.
/**
* Abstract base implementation of the TestDescriptor interface.
*/
public abstract class AbstractTestDescriptor implements TestDescriptor {
/**
* Constructor for creating test descriptors.
* @param uniqueId unique identifier for this descriptor
* @param displayName display name for this descriptor
*/
protected AbstractTestDescriptor(UniqueId uniqueId, String displayName);
/**
* Constructor with optional source.
* @param uniqueId unique identifier for this descriptor
* @param displayName display name for this descriptor
* @param source optional test source
*/
protected AbstractTestDescriptor(UniqueId uniqueId, String displayName, TestSource source);
// Implements all TestDescriptor methods with sensible defaults
/**
* Add a tag to this descriptor.
* @param tag the tag to add
*/
protected void addTag(TestTag tag);
/**
* Set the source for this descriptor.
* @param source the test source
*/
protected void setSource(TestSource source);
}Specialized test descriptor implementation for test engine root descriptors.
/**
* TestDescriptor implementation for test engine root descriptors.
*/
public class EngineDescriptor extends AbstractTestDescriptor {
/**
* Create an engine descriptor.
* @param uniqueId unique ID for the engine (should be engine root)
* @param displayName display name for the engine
*/
public EngineDescriptor(UniqueId uniqueId, String displayName);
/**
* Engine descriptors are always containers.
* @return Type.CONTAINER
*/
@Override
public final Type getType();
}Various implementations of the TestSource interface for different types of test origins.
/**
* Test source for Java classes.
*/
public class ClassSource implements TestSource {
public static ClassSource from(Class<?> javaClass);
public static ClassSource from(String className);
public Class<?> getJavaClass();
public String getClassName();
}
/**
* Test source for Java methods.
*/
public class MethodSource implements TestSource {
public static MethodSource from(Method javaMethod);
public static MethodSource from(Class<?> javaClass, Method javaMethod);
public static MethodSource from(Class<?> javaClass, String methodName);
public static MethodSource from(String className, String methodName);
public static MethodSource from(String className, String methodName, String methodParameterTypes);
public Class<?> getJavaClass();
public String getClassName();
public Method getJavaMethod();
public String getMethodName();
public String getMethodParameterTypes();
}
/**
* Test source for files.
*/
public class FileSource implements TestSource {
public static FileSource from(File file);
public static FileSource from(File file, FilePosition position);
public File getFile();
public Optional<FilePosition> getPosition();
}
/**
* Test source for directories.
*/
public class DirectorySource implements TestSource {
public static DirectorySource from(File directory);
public File getDirectory();
}
/**
* Test source for packages.
*/
public class PackageSource implements TestSource {
public static PackageSource from(String packageName);
public String getPackageName();
}
/**
* Test source for URIs.
*/
public class UriSource implements TestSource {
public static UriSource from(URI uri);
public URI getUri();
}
/**
* Test source for classpath resources.
*/
public class ClasspathResourceSource implements TestSource {
public static ClasspathResourceSource from(String classpathResourceName);
public static ClasspathResourceSource from(String classpathResourceName, FilePosition position);
public String getClasspathResourceName();
public Optional<FilePosition> getPosition();
}
/**
* Composite test source combining multiple sources.
*/
public class CompositeTestSource implements TestSource {
public static CompositeTestSource from(TestSource... sources);
public List<TestSource> getSources();
}Usage Example:
// Create various test sources
TestSource classSource = ClassSource.from(MyTestClass.class);
TestSource methodSource = MethodSource.from(MyTestClass.class, "testMethod");
TestSource fileSource = FileSource.from(new File("MyTest.java"));
// Use in test descriptors
TestDescriptor classDescriptor = new MyClassDescriptor(
uniqueId, "MyTestClass", classSource
);
TestDescriptor methodDescriptor = new MyMethodDescriptor(
methodId, "testMethod", methodSource
);Built-in support for traversing test descriptor hierarchies using the visitor pattern.
/**
* Composite visitor that combines multiple visitors.
*/
public class CompositeTestDescriptorVisitor implements TestDescriptor.Visitor {
/**
* Create a composite visitor from multiple visitors.
* @param visitors the visitors to combine
* @return composite visitor
*/
public static CompositeTestDescriptorVisitor of(TestDescriptor.Visitor... visitors);
@Override
public void visit(TestDescriptor descriptor, Runnable remove);
}Usage Example:
// Traverse descriptor hierarchy
engineDescriptor.accept(new TestDescriptor.Visitor() {
@Override
public void visit(TestDescriptor descriptor, Runnable remove) {
System.out.println("Visiting: " + descriptor.getDisplayName());
// Conditionally remove descriptors
if (shouldRemove(descriptor)) {
remove.run();
}
}
});
// Using composite visitor
TestDescriptor.Visitor loggingVisitor = (descriptor, remove) ->
System.out.println("Processing: " + descriptor.getDisplayName());
TestDescriptor.Visitor filteringVisitor = (descriptor, remove) -> {
if (descriptor.getTags().contains(TestTag.create("disabled"))) {
remove.run();
}
};
CompositeTestDescriptorVisitor composite = CompositeTestDescriptorVisitor.of(
loggingVisitor, filteringVisitor
);
engineDescriptor.accept(composite);Utility methods for navigating and querying test descriptor hierarchies.
Usage Example:
// Check if descriptor contains executable tests
if (TestDescriptor.containsTests(containerDescriptor)) {
// Process container with tests
}
// Find specific descriptors
Optional<TestDescriptor> specificTest = engineDescriptor.findByUniqueId(
UniqueId.parse("[engine:my-engine]/[class:MyClass]/[method:testMethod]")
);
// Get all leaf test descriptors
List<TestDescriptor> allTests = new ArrayList<>();
engineDescriptor.accept((descriptor, remove) -> {
if (descriptor.getType() == TestDescriptor.Type.TEST &&
descriptor.getChildren().isEmpty()) {
allTests.add(descriptor);
}
});
// Count tests and containers
long testCount = allTests.size();
long containerCount = getAllDescriptors(engineDescriptor).stream()
.filter(d -> d.getType() == TestDescriptor.Type.CONTAINER)
.count();Install with Tessl CLI
npx tessl i tessl/maven-org-junit-platform--junit-platform-engine