CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-junit-jupiter--junit-jupiter-api

JUnit Jupiter API for writing tests - Core API module of JUnit 5 that provides annotations, assertions, and test lifecycle management

Pending
Overview
Eval results
Files

extension-framework.mddocs/

Extension Framework

Comprehensive extension system for custom test behavior, dependency injection, lifecycle callbacks, and test monitoring. The extension framework allows developers to extend JUnit Jupiter's functionality through well-defined extension points and provides a robust context system for sharing data between extensions and tests.

Capabilities

Extension Registration

Register extensions declaratively or programmatically to extend test functionality.

/**
 * Declarative extension registration - registers extension class
 */
@ExtendWith(MyExtension.class)
@ExtendWith({TimingExtension.class, LoggingExtension.class})

/**
 * Programmatic extension registration - registers extension instance
 * Must be applied to static fields
 */
@RegisterExtension
static final MyExtension extension = new MyExtension("config");

@RegisterExtension
static final TemporaryFolder tempFolder = new TemporaryFolder();

Usage Examples:

// Declarative registration at class level
@ExtendWith({DatabaseExtension.class, MockitoExtension.class})
class UserServiceTest {

    // Programmatic registration with configuration
    @RegisterExtension
    static final TimerExtension timer = new TimerExtension()
        .withTimeout(Duration.ofSeconds(10))
        .withLogging(true);

    @Test
    void testUserCreation() {
        // Extensions are automatically applied
        UserService service = new UserService();
        User user = service.createUser("john", "john@example.com");
        assertNotNull(user);
    }
}

// Method-level registration
class SpecificTest {
    
    @Test
    @ExtendWith(RetryExtension.class)
    void flakyNetworkTest() {
        // Only this test uses retry extension
        NetworkService.connect();
    }
}

Core Extension Interface

Base interface that all extensions must implement.

/**
 * Marker interface for all JUnit Jupiter extensions
 */
interface Extension {
    // Marker interface - no methods to implement
}

Lifecycle Callback Extensions

Extensions that hook into test execution lifecycle at various points.

/**
 * Executed before all tests in a container
 */
interface BeforeAllCallback extends Extension {
    void beforeAll(ExtensionContext context) throws Exception;
}

/**
 * Executed after all tests in a container
 */
interface AfterAllCallback extends Extension {
    void afterAll(ExtensionContext context) throws Exception;
}

/**
 * Executed before each test method
 */
interface BeforeEachCallback extends Extension {
    void beforeEach(ExtensionContext context) throws Exception;
}

/**
 * Executed after each test method
 */
interface AfterEachCallback extends Extension {
    void afterEach(ExtensionContext context) throws Exception;
}

/**
 * Executed immediately before test method execution
 */
interface BeforeTestExecutionCallback extends Extension {
    void beforeTestExecution(ExtensionContext context) throws Exception;
}

/**
 * Executed immediately after test method execution
 */
interface AfterTestExecutionCallback extends Extension {
    void afterTestExecution(ExtensionContext context) throws Exception;
}

Usage Examples:

// Database setup/teardown extension
public class DatabaseExtension implements BeforeAllCallback, AfterAllCallback,
                                         BeforeEachCallback, AfterEachCallback {
    
    private static Database database;
    
    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        database = Database.create();
        database.start();
        context.getStore(NAMESPACE).put("database", database);
    }
    
    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        database.beginTransaction();
    }
    
    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        database.rollback();
    }
    
    @Override
    public void afterAll(ExtensionContext context) throws Exception {
        database.stop();
    }
    
    private static final Namespace NAMESPACE = Namespace.create("database");
}

// Timing extension
public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
    
    @Override
    public void beforeTestExecution(ExtensionContext context) throws Exception {
        long startTime = System.currentTimeMillis();
        context.getStore(NAMESPACE).put("startTime", startTime);
    }
    
    @Override
    public void afterTestExecution(ExtensionContext context) throws Exception {
        long startTime = context.getStore(NAMESPACE).get("startTime", Long.class);
        long duration = System.currentTimeMillis() - startTime;
        System.out.printf("Test %s took %d ms%n", context.getDisplayName(), duration);
    }
    
    private static final Namespace NAMESPACE = Namespace.create("timing");
}

Parameter Resolution

Extensions for dependency injection into test constructors and methods.

/**
 * Resolves parameters for test constructors and methods
 */
interface ParameterResolver extends Extension {
    /**
     * Determine if this resolver supports the given parameter
     */
    boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) 
        throws org.junit.jupiter.api.extension.ParameterResolutionException;
    
    /**
     * Resolve the parameter value
     */
    Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) 
        throws org.junit.jupiter.api.extension.ParameterResolutionException;
}

/**
 * Context information about the parameter being resolved
 */
interface ParameterContext {
    java.lang.reflect.Parameter getParameter();
    int getIndex();
    java.util.Optional<Object> getTarget();
}

Usage Examples:

// Custom parameter resolver for injecting test data
public class TestDataResolver implements ParameterResolver {
    
    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
        return parameterContext.getParameter().getType() == TestData.class;
    }
    
    @Override
    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
        String testName = extensionContext.getDisplayName();
        return TestDataFactory.createFor(testName);
    }
}

// Usage in test
@ExtendWith(TestDataResolver.class)
class ParameterizedTest {
    
    @Test
    void testWithInjectedData(TestData data) {
        // TestData automatically injected by resolver
        assertNotNull(data);
        assertTrue(data.isValid());
    }
    
    @Test  
    void testWithMultipleParameters(TestData data, TestInfo testInfo) {
        // Mix custom and built-in parameter resolvers
        assertEquals(testInfo.getDisplayName(), data.getTestName());
    }
}

// Built-in parameter resolvers inject these types automatically:
@Test
void testWithBuiltInParameters(TestInfo testInfo, TestReporter testReporter, 
                              RepetitionInfo repetitionInfo) {
    testReporter.publishEntry("test", testInfo.getDisplayName());
    if (repetitionInfo != null) {
        testReporter.publishEntry("repetition", String.valueOf(repetitionInfo.getCurrentRepetition()));
    }
}

Test Instance Management

Extensions for controlling test instance creation and lifecycle.

/**
 * Custom test instance creation
 */
interface TestInstanceFactory extends Extension {
    Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) 
        throws org.junit.jupiter.api.extension.TestInstantiationException;
}

/**
 * Post-process test instances after creation
 */
interface TestInstancePostProcessor extends Extension {
    void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception;
}

/**
 * Called before test instance construction
 * Provides access to test-specific data when using TEST_METHOD scope
 */
interface TestInstancePreConstructCallback extends TestInstantiationAwareExtension {
    /**
     * Callback invoked prior to test instances being constructed
     * @param factoryContext the context for the test instance about to be instantiated
     * @param context the current extension context
     */
    void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) throws Exception;
}

/**
 * Called before test instance destruction
 * Handles cleanup of resources created for test instances
 */
interface TestInstancePreDestroyCallback extends Extension {
    /**
     * Callback for processing test instances before they are destroyed
     * Called once per ExtensionContext even for nested tests
     * @param context the current extension context
     */
    void preDestroyTestInstance(ExtensionContext context) throws Exception;
    
    /**
     * Utility method for processing all test instances about to be destroyed
     * Ensures correct handling regardless of lifecycle mode
     * @param context the current extension context
     * @param callback invoked for every test instance about to be destroyed
     */
    static void preDestroyTestInstances(ExtensionContext context, java.util.function.Consumer<Object> callback);
}

/**
 * Base interface for extensions aware of test instantiation
 * Allows control over ExtensionContext scope during instantiation
 */
interface TestInstantiationAwareExtension extends Extension {
    /**
     * Determines extension context scope during test instantiation
     * @param rootContext the root extension context for configuration access
     * @return the desired extension context scope
     */
    default ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) {
        return ExtensionContextScope.DEFAULT;
    }
    
    /**
     * Extension context scope options for test instantiation
     */
    enum ExtensionContextScope {
        DEFAULT, // Legacy behavior - class-scoped context
        TEST_METHOD // Test-method-scoped context (recommended)
    }
}

/**
 * Context for test instance factory
 */
interface TestInstanceFactoryContext {
    Class<?> getTestClass();
    java.util.Optional<Object> getOuterInstance();
}

Usage Examples:

// Custom test instance factory with dependency injection
public class DITestInstanceFactory implements TestInstanceFactory {
    
    private final DIContainer container;
    
    public DITestInstanceFactory(DIContainer container) {
        this.container = container;
    }
    
    @Override
    public Object createTestInstance(TestInstanceFactoryContext factoryContext, 
                                   ExtensionContext extensionContext) throws TestInstantiationException {
        Class<?> testClass = factoryContext.getTestClass();
        try {
            return container.createInstance(testClass);
        } catch (Exception e) {
            throw new org.junit.jupiter.api.extension.TestInstantiationException("Failed to create test instance", e);
        }
    }
}

// Post-processor for field injection
public class FieldInjectionExtension implements TestInstancePostProcessor {
    
    @Override
    public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
        Class<?> testClass = testInstance.getClass();
        
        for (java.lang.reflect.Field field : testClass.getDeclaredFields()) {
            if (field.isAnnotationPresent(Inject.class)) {
                field.setAccessible(true);
                Object dependency = resolveDependency(field.getType());
                field.set(testInstance, dependency);
            }
        }
    }
    
    private Object resolveDependency(Class<?> type) {
        // Dependency resolution logic
        return DIContainer.getInstance().resolve(type);
    }
}

// Test instance lifecycle management extension
public class InstanceLifecycleExtension implements TestInstancePreConstructCallback, 
                                                   TestInstancePreDestroyCallback {
    
    @Override
    public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) {
        // Use test-method scope for better isolation
        return ExtensionContextScope.TEST_METHOD;
    }
    
    @Override
    public void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) throws Exception {
        Class<?> testClass = factoryContext.getTestClass();
        System.out.println("Preparing to construct test instance: " + testClass.getSimpleName());
        
        // Initialize test-specific resources before construction
        String testName = context.getTestMethod().map(method -> method.getName()).orElse("class-level");
        TestResources resources = TestResources.create(testName);
        context.getStore(NAMESPACE).put("test.resources", resources);
    }
    
    @Override
    public void preDestroyTestInstance(ExtensionContext context) throws Exception {
        // Clean up all test instances using the utility method
        TestInstancePreDestroyCallback.preDestroyTestInstances(context, testInstance -> {
            System.out.println("Cleaning up test instance: " + testInstance.getClass().getSimpleName());
            
            // Release any resources associated with this instance
            if (testInstance instanceof ResourceHolder) {
                ((ResourceHolder) testInstance).releaseResources();
            }
        });
        
        // Clean up shared resources from store
        TestResources resources = context.getStore(NAMESPACE).get("test.resources", TestResources.class);
        if (resources != null) {
            resources.cleanup();
        }
    }
    
    private static final Namespace NAMESPACE = Namespace.create("instance", "lifecycle");
}

Conditional Execution

Extensions for runtime test execution decisions.

/**
 * Determine whether a test should be executed
 */
interface ExecutionCondition extends Extension {
    ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context);
}

/**
 * Result of condition evaluation
 */
class ConditionEvaluationResult {
    static ConditionEvaluationResult enabled(String reason);
    static ConditionEvaluationResult disabled(String reason);
    
    boolean isDisabled();
    java.util.Optional<String> getReason();
}

Usage Examples:

// Custom execution condition
public class DatabaseAvailableCondition implements ExecutionCondition {
    
    @Override
    public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
        if (isDatabaseAvailable()) {
            return ConditionEvaluationResult.enabled("Database is available");
        } else {
            return ConditionEvaluationResult.disabled("Database is not available");
        }
    }
    
    private boolean isDatabaseAvailable() {
        try {
            java.sql.Connection conn = java.sql.DriverManager.getConnection("jdbc:h2:mem:test");
            conn.close();
            return true;
        } catch (java.sql.SQLException e) {
            return false;
        }
    }
}

@ExtendWith(DatabaseAvailableCondition.class)
class DatabaseTest {
    
    @Test
    void testDatabaseOperation() {
        // Only runs if database is available
        assertTrue(true);
    }
}

Exception Handling

Extensions for handling exceptions during test execution.

/**
 * Handle exceptions thrown during test execution
 */
interface TestExecutionExceptionHandler extends Extension {
    void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;
}

/**
 * Handle exceptions thrown during lifecycle method execution
 */
interface LifecycleMethodExecutionExceptionHandler extends Extension {
    void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;
    void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;
    void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;
    void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;
}

Usage Examples:

// Retry extension for handling flaky tests
public class RetryExtension implements TestExecutionExceptionHandler {
    
    private static final int MAX_RETRIES = 3;
    
    @Override
    public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
        Namespace namespace = Namespace.create("retry");
        Store store = context.getStore(namespace);
        
        int attempts = store.getOrDefault("attempts", Integer.class, 0);
        if (attempts < MAX_RETRIES && isRetryableException(throwable)) {
            store.put("attempts", attempts + 1);
            System.out.printf("Retrying test %s (attempt %d/%d)%n", 
                context.getDisplayName(), attempts + 1, MAX_RETRIES);
            // Re-throw as TestAbortedException to retry
            throw new TestAbortedException("Retrying due to: " + throwable.getMessage());
        } else {
            // Max retries exceeded or non-retryable exception
            throw throwable;
        }
    }
    
    private boolean isRetryableException(Throwable throwable) {
        return throwable instanceof java.io.IOException || 
               throwable instanceof java.net.ConnectException;
    }
}

Test Watching

Extensions for observing test execution results.

/**
 * Observe test execution outcomes
 */
interface TestWatcher extends Extension {
    /**
     * Called when a test is disabled
     */
    default void testDisabled(ExtensionContext context, java.util.Optional<String> reason) {}
    
    /**
     * Called when a test completes successfully
     */
    default void testSuccessful(ExtensionContext context) {}
    
    /**
     * Called when a test is aborted
     */
    default void testAborted(ExtensionContext context, Throwable cause) {}
    
    /**
     * Called when a test fails
     */
    default void testFailed(ExtensionContext context, Throwable cause) {}
}

Usage Examples:

// Test result reporter extension
public class TestResultReporter implements TestWatcher {
    
    private final List<TestResult> results = new ArrayList<>();
    
    @Override
    public void testSuccessful(ExtensionContext context) {
        results.add(new TestResult(context.getDisplayName(), "PASSED", null));
        System.out.println("✓ " + context.getDisplayName());
    }
    
    @Override
    public void testFailed(ExtensionContext context, Throwable cause) {
        results.add(new TestResult(context.getDisplayName(), "FAILED", cause.getMessage()));
        System.out.println("✗ " + context.getDisplayName() + ": " + cause.getMessage());
    }
    
    @Override
    public void testAborted(ExtensionContext context, Throwable cause) {
        results.add(new TestResult(context.getDisplayName(), "ABORTED", cause.getMessage()));
        System.out.println("○ " + context.getDisplayName() + ": " + cause.getMessage());
    }
    
    @Override
    public void testDisabled(ExtensionContext context, java.util.Optional<String> reason) {
        String reasonText = reason.orElse("No reason provided");
        results.add(new TestResult(context.getDisplayName(), "DISABLED", reasonText));
        System.out.println("- " + context.getDisplayName() + ": " + reasonText);
    }
    
    public java.util.List<TestResult> getResults() {
        return java.util.Collections.unmodifiableList(results);
    }
}

Method Invocation Interception

Extensions for intercepting and modifying test method invocations.

/**
 * Intercept test method invocations
 */
interface InvocationInterceptor extends Extension {
    /**
     * Intercept test method invocation
     */
    default void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<java.lang.reflect.Method> invocationContext, 
                                   ExtensionContext extensionContext) throws Throwable {
        invocation.proceed();
    }
    
    /**
     * Intercept test template method invocation
     */
    default <T> T interceptTestTemplateMethod(Invocation<T> invocation, ReflectiveInvocationContext<java.lang.reflect.Method> invocationContext,
                                            ExtensionContext extensionContext) throws Throwable {
        return invocation.proceed();
    }
    
    /**
     * Intercept test factory method invocation
     */
    default <T> T interceptTestFactoryMethod(Invocation<T> invocation, ReflectiveInvocationContext<java.lang.reflect.Method> invocationContext,
                                           ExtensionContext extensionContext) throws Throwable {
        return invocation.proceed();
    }
    
    /**
     * Intercept dynamic test invocation
     */
    default void interceptDynamicTest(Invocation<Void> invocation, DynamicTestInvocationContext invocationContext,
                                    ExtensionContext extensionContext) throws Throwable {
        invocation.proceed();
    }
}

Extension Context

Comprehensive context information and utilities available to all extensions.

/**
 * Context information for extension execution
 */
interface ExtensionContext {
    /**
     * Get the unique ID of this context
     */
    String getUniqueId();
    
    /**
     * Get the display name
     */
    String getDisplayName();
    
    /**
     * Get all tags associated with this context
     */
    java.util.Set<String> getTags();
    
    /**
     * Get the parent context (if any)
     */
    java.util.Optional<ExtensionContext> getParent();
    
    /**
     * Get the root context
     */
    ExtensionContext getRoot();
    
    /**
     * Get the test element (class, method, etc.)
     */
    java.util.Optional<java.lang.reflect.AnnotatedElement> getElement();
    
    /**
     * Get the test class
     */
    java.util.Optional<Class<?>> getTestClass();
    
    /**
     * Get the test method (if this is a method context)
     */
    java.util.Optional<java.lang.reflect.Method> getTestMethod();
    
    /**
     * Get the test instance (if available)
     */
    java.util.Optional<Object> getTestInstance();
    
    /**
     * Get all test instances in the hierarchy
     */
    java.util.Optional<TestInstances> getTestInstances();
    
    /**
     * Get a namespace-scoped data store
     */
    Store getStore(Namespace namespace);
    
    /**
     * Publish a report entry
     */
    void publishReportEntry(java.util.Map<String, String> map);
    void publishReportEntry(String key, String value);
    
    /**
     * Get configuration parameter value
     */
    java.util.Optional<String> getConfigurationParameter(String key);
    
    /**
     * Get configuration parameter with type conversion
     */
    <T> java.util.Optional<T> getConfigurationParameter(String key, java.util.function.Function<String, T> transformer);
    
    /**
     * Get the ExecutableInvoker for this context
     * Allows dynamic invocation of methods and constructors with parameter resolution
     */
    ExecutableInvoker getExecutableInvoker();
}

Timeout Handling Extensions

Extensions for handling timeouts and thread interrupts.

/**
 * Called before thread interruption during timeout handling
 */
interface PreInterruptCallback extends Extension {
    /**
     * Property name for enabling thread dump on timeout
     */
    String THREAD_DUMP_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.timeout.threaddump.enabled";
    
    /**
     * Callback invoked before a thread is interrupted
     * @param preInterruptContext context with target thread information
     * @param extensionContext the extension context for the callback
     */
    void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) throws Exception;
}

/**
 * Context information for thread interruption
 */
interface PreInterruptContext {
    /**
     * Get the thread that will be interrupted
     * @return the target thread
     */
    Thread getThreadToInterrupt();
}

Usage Examples:

// Thread dump extension for timeout debugging
public class TimeoutDebuggingExtension implements PreInterruptCallback {
    
    @Override
    public void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) throws Exception {
        Thread targetThread = preInterruptContext.getThreadToInterrupt();
        System.err.printf("About to interrupt thread '%s' due to timeout in %s%n", 
            targetThread.getName(), extensionContext.getDisplayName());
        
        // Dump thread stack for debugging
        StackTraceElement[] stack = targetThread.getStackTrace();
        System.err.println("Thread stack trace:");
        for (StackTraceElement element : stack) {
            System.err.println("  at " + element);
        }
        
        // Log additional context information
        extensionContext.publishReportEntry("timeout.thread", targetThread.getName());
        extensionContext.publishReportEntry("timeout.test", extensionContext.getDisplayName());
    }
}

// Usage
@ExtendWith(TimeoutDebuggingExtension.class)
class TimeoutTest {
    
    @Test
    @Timeout(value = 1, unit = TimeUnit.SECONDS)
    void testThatMightTimeout() {
        // Test logic that might exceed timeout
        while (true) {
            // Infinite loop to demonstrate timeout
        }
    }
}

Executable Invocation

Utility for invoking methods and constructors with parameter resolution.

/**
 * Allows invoking executables with dynamic parameter resolution
 */
interface ExecutableInvoker {
    /**
     * Invoke static method with parameter resolution
     * @param method the method to invoke
     * @return method result
     */
    default Object invoke(java.lang.reflect.Method method) {
        return invoke(method, null);
    }
    
    /**
     * Invoke method with parameter resolution
     * @param method the method to invoke
     * @param target the target instance (null for static methods)
     * @return method result
     */
    Object invoke(java.lang.reflect.Method method, Object target);
    
    /**
     * Invoke constructor with parameter resolution
     * @param constructor the constructor to invoke
     * @return constructed instance
     */
    default <T> T invoke(java.lang.reflect.Constructor<T> constructor) {
        return invoke(constructor, null);
    }
    
    /**
     * Invoke constructor with outer instance and parameter resolution
     * @param constructor the constructor to invoke
     * @param outerInstance the outer instance for inner classes
     * @return constructed instance
     */
    <T> T invoke(java.lang.reflect.Constructor<T> constructor, Object outerInstance);
}

Usage Examples:

// Extension that demonstrates ExecutableInvoker usage
public class DynamicInvocationExtension implements BeforeEachCallback, ParameterResolver {
    
    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        // Get the ExecutableInvoker from the context
        ExecutableInvoker invoker = context.getExecutableInvoker();
        
        // Dynamically invoke setup methods with parameter resolution
        java.lang.reflect.Method[] methods = context.getRequiredTestClass().getDeclaredMethods();
        for (java.lang.reflect.Method method : methods) {
            if (method.isAnnotationPresent(DynamicSetup.class)) {
                Object testInstance = context.getRequiredTestInstance();
                invoker.invoke(method, testInstance);
            }
        }
    }
    
    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
        return parameterContext.getParameter().getType() == ExecutableInvoker.class;
    }
    
    @Override
    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
        return extensionContext.getExecutableInvoker();
    }
}

// Usage in tests
@ExtendWith(DynamicInvocationExtension.class)
class DynamicInvocationTest {
    
    @DynamicSetup
    void setupWithParameters(TestInfo testInfo, TestReporter reporter) {
        reporter.publishEntry("setup", "Dynamic setup called for " + testInfo.getDisplayName());
    }
    
    @Test
    void testWithInvoker(ExecutableInvoker invoker) throws Exception {
        // Use invoker to dynamically call methods
        java.lang.reflect.Method method = this.getClass().getDeclaredMethod("helperMethod", String.class);
        Object result = invoker.invoke(method, this);
        assertNotNull(result);
    }
    
    String helperMethod(String input) {
        return "Helper: " + input;
    }
}

Extension Container Annotations

Annotations for organizing extension declarations.

/**
 * Container for multiple @ExtendWith declarations
 * Optional since @ExtendWith is repeatable
 */
@Extensions({
    @ExtendWith(DatabaseExtension.class),
    @ExtendWith(MockitoExtension.class),
    @ExtendWith(TimingExtension.class)
})
@interface Extensions {
    ExtendWith[] value();
}

Usage Examples:

// Using @Extensions container (optional)
@Extensions({
    @ExtendWith(DatabaseExtension.class),
    @ExtendWith(LoggingExtension.class),
    @ExtendWith(MetricsExtension.class)
})
class MultiExtensionTest {
    
    @Test
    void testWithMultipleExtensions() {
        // All extensions are applied
        assertTrue(true);
    }
}

// Alternative using repeatable @ExtendWith (recommended)
@ExtendWith(DatabaseExtension.class)
@ExtendWith(LoggingExtension.class)
@ExtendWith(MetricsExtension.class)
class RepeatedExtensionTest {
    
    @Test
    void testWithRepeatedExtensions() {
        // Same effect as @Extensions container
        assertTrue(true);
    }
}

Extension Context Store

Scoped data storage for sharing information between extensions and tests.

/**
 * Namespace-scoped key-value store for extension data
 */
interface Store {
    /**
     * Get value by key
     */
    Object get(Object key);
    
    /**
     * Get typed value by key
     */
    <V> V get(Object key, Class<V> requiredType);
    
    /**
     * Get value with default
     */
    <K, V> Object getOrDefault(K key, Class<V> requiredType, V defaultValue);
    
    /**
     * Put key-value pair
     */
    void put(Object key, Object value);
    
    /**
     * Get or compute value if absent
     */
    Object getOrComputeIfAbsent(Object key, java.util.function.Function<Object, Object> defaultCreator);
    
    /**
     * Get or compute typed value if absent
     */
    <K, V> V getOrComputeIfAbsent(K key, java.util.function.Function<K, V> defaultCreator, Class<V> requiredType);
    
    /**
     * Remove value by key
     */
    Object remove(Object key);
    
    /**
     * Remove typed value by key
     */
    <V> V remove(Object key, Class<V> requiredType);
}

/**
 * Namespace for scoping store data
 */
class Namespace {
    /**
     * Global namespace
     */
    static final Namespace GLOBAL = Namespace.create("global");
    
    /**
     * Create namespace from parts
     */
    static Namespace create(Object... parts);
}

Usage Examples:

public class SharedDataExtension implements BeforeAllCallback, BeforeEachCallback {
    
    private static final Namespace NAMESPACE = Namespace.create("shared", "data");
    
    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        // Store class-level shared data
        ExpensiveResource resource = new ExpensiveResource();
        context.getStore(NAMESPACE).put("resource", resource);
    }
    
    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        // Access shared data and create per-test data
        ExpensiveResource resource = context.getStore(NAMESPACE)
            .get("resource", ExpensiveResource.class);
        
        TestData testData = resource.createTestData(context.getDisplayName());
        context.getStore(NAMESPACE).put("testData", testData);
    }
    
    public static TestData getTestData(ExtensionContext context) {
        return context.getStore(NAMESPACE).get("testData", TestData.class);
    }
}

Exception Types

/**
 * Exception thrown when extension configuration is invalid
 */
class ExtensionConfigurationException extends JUnitException {
    ExtensionConfigurationException(String message);
    ExtensionConfigurationException(String message, Throwable cause);
}

/**
 * Exception thrown when parameter resolution fails
 */
class ParameterResolutionException extends JUnitException {
    ParameterResolutionException(String message);
    ParameterResolutionException(String message, Throwable cause);
}

/**
 * Exception thrown when test instantiation fails
 */
class TestInstantiationException extends JUnitException {
    TestInstantiationException(String message);
    TestInstantiationException(String message, Throwable cause);
}

/**
 * Exception thrown when extension context operations fail
 */
class ExtensionContextException extends JUnitException {
    ExtensionContextException(String message);
    ExtensionContextException(String message, Throwable cause);
}

Types

Test Instance Management

/**
 * Container for test instances in nested class hierarchies
 */
class TestInstances {
    /**
     * Get the innermost (leaf) test instance
     */
    Object getInnermostInstance();
    
    /**
     * Get all test instances from outermost to innermost
     */
    java.util.List<Object> getAllInstances();
    
    /**
     * Find instance of specified type
     */
    <T> java.util.Optional<T> findInstance(Class<T> requiredType);
}

Extension Support Utilities

/**
 * Base class for type-based parameter resolvers
 * @param <T> the type of the parameter supported by this ParameterResolver
 */
abstract class TypeBasedParameterResolver<T> implements ParameterResolver {
    
    /**
     * Constructor that automatically discovers the supported parameter type from generic declaration
     */
    public TypeBasedParameterResolver();
    
    /**
     * Final implementation that checks parameter type against the discovered type
     */
    @Override
    public final boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext);
    
    /**
     * Resolve the parameter - must be implemented by subclasses
     * @param parameterContext the context for the parameter for which an argument should be resolved
     * @param extensionContext the extension context for the method
     * @return the resolved parameter object
     * @throws ParameterResolutionException if an error occurs resolving the parameter
     */
    @Override
    public abstract T resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
            throws ParameterResolutionException;
}

Install with Tessl CLI

npx tessl i tessl/maven-org-junit-jupiter--junit-jupiter-api

docs

conditional-execution.md

core-testing.md

extension-framework.md

index.md

parallel-io.md

tile.json