JUnit Jupiter API for writing tests - Core API module of JUnit 5 that provides annotations, assertions, and test lifecycle management
—
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.
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();
}
}Base interface that all extensions must implement.
/**
* Marker interface for all JUnit Jupiter extensions
*/
interface Extension {
// Marker interface - no methods to implement
}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");
}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()));
}
}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");
}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);
}
}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;
}
}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);
}
}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();
}
}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();
}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
}
}
}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;
}
}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);
}
}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 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);
}/**
* 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);
}/**
* 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