or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

conditional-execution.mdcore-testing.mdextension-framework.mdindex.mdparallel-io.md

extension-framework.mddocs/

0

# Extension Framework

1

2

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.

3

4

## Capabilities

5

6

### Extension Registration

7

8

Register extensions declaratively or programmatically to extend test functionality.

9

10

```java { .api }

11

/**

12

* Declarative extension registration - registers extension class

13

*/

14

@ExtendWith(MyExtension.class)

15

@ExtendWith({TimingExtension.class, LoggingExtension.class})

16

17

/**

18

* Programmatic extension registration - registers extension instance

19

* Must be applied to static fields

20

*/

21

@RegisterExtension

22

static final MyExtension extension = new MyExtension("config");

23

24

@RegisterExtension

25

static final TemporaryFolder tempFolder = new TemporaryFolder();

26

```

27

28

**Usage Examples:**

29

30

```java

31

// Declarative registration at class level

32

@ExtendWith({DatabaseExtension.class, MockitoExtension.class})

33

class UserServiceTest {

34

35

// Programmatic registration with configuration

36

@RegisterExtension

37

static final TimerExtension timer = new TimerExtension()

38

.withTimeout(Duration.ofSeconds(10))

39

.withLogging(true);

40

41

@Test

42

void testUserCreation() {

43

// Extensions are automatically applied

44

UserService service = new UserService();

45

User user = service.createUser("john", "john@example.com");

46

assertNotNull(user);

47

}

48

}

49

50

// Method-level registration

51

class SpecificTest {

52

53

@Test

54

@ExtendWith(RetryExtension.class)

55

void flakyNetworkTest() {

56

// Only this test uses retry extension

57

NetworkService.connect();

58

}

59

}

60

```

61

62

### Core Extension Interface

63

64

Base interface that all extensions must implement.

65

66

```java { .api }

67

/**

68

* Marker interface for all JUnit Jupiter extensions

69

*/

70

interface Extension {

71

// Marker interface - no methods to implement

72

}

73

```

74

75

### Lifecycle Callback Extensions

76

77

Extensions that hook into test execution lifecycle at various points.

78

79

```java { .api }

80

/**

81

* Executed before all tests in a container

82

*/

83

interface BeforeAllCallback extends Extension {

84

void beforeAll(ExtensionContext context) throws Exception;

85

}

86

87

/**

88

* Executed after all tests in a container

89

*/

90

interface AfterAllCallback extends Extension {

91

void afterAll(ExtensionContext context) throws Exception;

92

}

93

94

/**

95

* Executed before each test method

96

*/

97

interface BeforeEachCallback extends Extension {

98

void beforeEach(ExtensionContext context) throws Exception;

99

}

100

101

/**

102

* Executed after each test method

103

*/

104

interface AfterEachCallback extends Extension {

105

void afterEach(ExtensionContext context) throws Exception;

106

}

107

108

/**

109

* Executed immediately before test method execution

110

*/

111

interface BeforeTestExecutionCallback extends Extension {

112

void beforeTestExecution(ExtensionContext context) throws Exception;

113

}

114

115

/**

116

* Executed immediately after test method execution

117

*/

118

interface AfterTestExecutionCallback extends Extension {

119

void afterTestExecution(ExtensionContext context) throws Exception;

120

}

121

```

122

123

**Usage Examples:**

124

125

```java

126

// Database setup/teardown extension

127

public class DatabaseExtension implements BeforeAllCallback, AfterAllCallback,

128

BeforeEachCallback, AfterEachCallback {

129

130

private static Database database;

131

132

@Override

133

public void beforeAll(ExtensionContext context) throws Exception {

134

database = Database.create();

135

database.start();

136

context.getStore(NAMESPACE).put("database", database);

137

}

138

139

@Override

140

public void beforeEach(ExtensionContext context) throws Exception {

141

database.beginTransaction();

142

}

143

144

@Override

145

public void afterEach(ExtensionContext context) throws Exception {

146

database.rollback();

147

}

148

149

@Override

150

public void afterAll(ExtensionContext context) throws Exception {

151

database.stop();

152

}

153

154

private static final Namespace NAMESPACE = Namespace.create("database");

155

}

156

157

// Timing extension

158

public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {

159

160

@Override

161

public void beforeTestExecution(ExtensionContext context) throws Exception {

162

long startTime = System.currentTimeMillis();

163

context.getStore(NAMESPACE).put("startTime", startTime);

164

}

165

166

@Override

167

public void afterTestExecution(ExtensionContext context) throws Exception {

168

long startTime = context.getStore(NAMESPACE).get("startTime", Long.class);

169

long duration = System.currentTimeMillis() - startTime;

170

System.out.printf("Test %s took %d ms%n", context.getDisplayName(), duration);

171

}

172

173

private static final Namespace NAMESPACE = Namespace.create("timing");

174

}

175

```

176

177

### Parameter Resolution

178

179

Extensions for dependency injection into test constructors and methods.

180

181

```java { .api }

182

/**

183

* Resolves parameters for test constructors and methods

184

*/

185

interface ParameterResolver extends Extension {

186

/**

187

* Determine if this resolver supports the given parameter

188

*/

189

boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)

190

throws org.junit.jupiter.api.extension.ParameterResolutionException;

191

192

/**

193

* Resolve the parameter value

194

*/

195

Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)

196

throws org.junit.jupiter.api.extension.ParameterResolutionException;

197

}

198

199

/**

200

* Context information about the parameter being resolved

201

*/

202

interface ParameterContext {

203

java.lang.reflect.Parameter getParameter();

204

int getIndex();

205

java.util.Optional<Object> getTarget();

206

}

207

```

208

209

**Usage Examples:**

210

211

```java

212

// Custom parameter resolver for injecting test data

213

public class TestDataResolver implements ParameterResolver {

214

215

@Override

216

public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {

217

return parameterContext.getParameter().getType() == TestData.class;

218

}

219

220

@Override

221

public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {

222

String testName = extensionContext.getDisplayName();

223

return TestDataFactory.createFor(testName);

224

}

225

}

226

227

// Usage in test

228

@ExtendWith(TestDataResolver.class)

229

class ParameterizedTest {

230

231

@Test

232

void testWithInjectedData(TestData data) {

233

// TestData automatically injected by resolver

234

assertNotNull(data);

235

assertTrue(data.isValid());

236

}

237

238

@Test

239

void testWithMultipleParameters(TestData data, TestInfo testInfo) {

240

// Mix custom and built-in parameter resolvers

241

assertEquals(testInfo.getDisplayName(), data.getTestName());

242

}

243

}

244

245

// Built-in parameter resolvers inject these types automatically:

246

@Test

247

void testWithBuiltInParameters(TestInfo testInfo, TestReporter testReporter,

248

RepetitionInfo repetitionInfo) {

249

testReporter.publishEntry("test", testInfo.getDisplayName());

250

if (repetitionInfo != null) {

251

testReporter.publishEntry("repetition", String.valueOf(repetitionInfo.getCurrentRepetition()));

252

}

253

}

254

```

255

256

### Test Instance Management

257

258

Extensions for controlling test instance creation and lifecycle.

259

260

```java { .api }

261

/**

262

* Custom test instance creation

263

*/

264

interface TestInstanceFactory extends Extension {

265

Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext)

266

throws org.junit.jupiter.api.extension.TestInstantiationException;

267

}

268

269

/**

270

* Post-process test instances after creation

271

*/

272

interface TestInstancePostProcessor extends Extension {

273

void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception;

274

}

275

276

/**

277

* Called before test instance construction

278

* Provides access to test-specific data when using TEST_METHOD scope

279

*/

280

interface TestInstancePreConstructCallback extends TestInstantiationAwareExtension {

281

/**

282

* Callback invoked prior to test instances being constructed

283

* @param factoryContext the context for the test instance about to be instantiated

284

* @param context the current extension context

285

*/

286

void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) throws Exception;

287

}

288

289

/**

290

* Called before test instance destruction

291

* Handles cleanup of resources created for test instances

292

*/

293

interface TestInstancePreDestroyCallback extends Extension {

294

/**

295

* Callback for processing test instances before they are destroyed

296

* Called once per ExtensionContext even for nested tests

297

* @param context the current extension context

298

*/

299

void preDestroyTestInstance(ExtensionContext context) throws Exception;

300

301

/**

302

* Utility method for processing all test instances about to be destroyed

303

* Ensures correct handling regardless of lifecycle mode

304

* @param context the current extension context

305

* @param callback invoked for every test instance about to be destroyed

306

*/

307

static void preDestroyTestInstances(ExtensionContext context, java.util.function.Consumer<Object> callback);

308

}

309

310

/**

311

* Base interface for extensions aware of test instantiation

312

* Allows control over ExtensionContext scope during instantiation

313

*/

314

interface TestInstantiationAwareExtension extends Extension {

315

/**

316

* Determines extension context scope during test instantiation

317

* @param rootContext the root extension context for configuration access

318

* @return the desired extension context scope

319

*/

320

default ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) {

321

return ExtensionContextScope.DEFAULT;

322

}

323

324

/**

325

* Extension context scope options for test instantiation

326

*/

327

enum ExtensionContextScope {

328

DEFAULT, // Legacy behavior - class-scoped context

329

TEST_METHOD // Test-method-scoped context (recommended)

330

}

331

}

332

333

/**

334

* Context for test instance factory

335

*/

336

interface TestInstanceFactoryContext {

337

Class<?> getTestClass();

338

java.util.Optional<Object> getOuterInstance();

339

}

340

```

341

342

**Usage Examples:**

343

344

```java

345

// Custom test instance factory with dependency injection

346

public class DITestInstanceFactory implements TestInstanceFactory {

347

348

private final DIContainer container;

349

350

public DITestInstanceFactory(DIContainer container) {

351

this.container = container;

352

}

353

354

@Override

355

public Object createTestInstance(TestInstanceFactoryContext factoryContext,

356

ExtensionContext extensionContext) throws TestInstantiationException {

357

Class<?> testClass = factoryContext.getTestClass();

358

try {

359

return container.createInstance(testClass);

360

} catch (Exception e) {

361

throw new org.junit.jupiter.api.extension.TestInstantiationException("Failed to create test instance", e);

362

}

363

}

364

}

365

366

// Post-processor for field injection

367

public class FieldInjectionExtension implements TestInstancePostProcessor {

368

369

@Override

370

public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {

371

Class<?> testClass = testInstance.getClass();

372

373

for (java.lang.reflect.Field field : testClass.getDeclaredFields()) {

374

if (field.isAnnotationPresent(Inject.class)) {

375

field.setAccessible(true);

376

Object dependency = resolveDependency(field.getType());

377

field.set(testInstance, dependency);

378

}

379

}

380

}

381

382

private Object resolveDependency(Class<?> type) {

383

// Dependency resolution logic

384

return DIContainer.getInstance().resolve(type);

385

}

386

}

387

388

// Test instance lifecycle management extension

389

public class InstanceLifecycleExtension implements TestInstancePreConstructCallback,

390

TestInstancePreDestroyCallback {

391

392

@Override

393

public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) {

394

// Use test-method scope for better isolation

395

return ExtensionContextScope.TEST_METHOD;

396

}

397

398

@Override

399

public void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) throws Exception {

400

Class<?> testClass = factoryContext.getTestClass();

401

System.out.println("Preparing to construct test instance: " + testClass.getSimpleName());

402

403

// Initialize test-specific resources before construction

404

String testName = context.getTestMethod().map(method -> method.getName()).orElse("class-level");

405

TestResources resources = TestResources.create(testName);

406

context.getStore(NAMESPACE).put("test.resources", resources);

407

}

408

409

@Override

410

public void preDestroyTestInstance(ExtensionContext context) throws Exception {

411

// Clean up all test instances using the utility method

412

TestInstancePreDestroyCallback.preDestroyTestInstances(context, testInstance -> {

413

System.out.println("Cleaning up test instance: " + testInstance.getClass().getSimpleName());

414

415

// Release any resources associated with this instance

416

if (testInstance instanceof ResourceHolder) {

417

((ResourceHolder) testInstance).releaseResources();

418

}

419

});

420

421

// Clean up shared resources from store

422

TestResources resources = context.getStore(NAMESPACE).get("test.resources", TestResources.class);

423

if (resources != null) {

424

resources.cleanup();

425

}

426

}

427

428

private static final Namespace NAMESPACE = Namespace.create("instance", "lifecycle");

429

}

430

```

431

432

### Conditional Execution

433

434

Extensions for runtime test execution decisions.

435

436

```java { .api }

437

/**

438

* Determine whether a test should be executed

439

*/

440

interface ExecutionCondition extends Extension {

441

ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context);

442

}

443

444

/**

445

* Result of condition evaluation

446

*/

447

class ConditionEvaluationResult {

448

static ConditionEvaluationResult enabled(String reason);

449

static ConditionEvaluationResult disabled(String reason);

450

451

boolean isDisabled();

452

java.util.Optional<String> getReason();

453

}

454

```

455

456

**Usage Examples:**

457

458

```java

459

// Custom execution condition

460

public class DatabaseAvailableCondition implements ExecutionCondition {

461

462

@Override

463

public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {

464

if (isDatabaseAvailable()) {

465

return ConditionEvaluationResult.enabled("Database is available");

466

} else {

467

return ConditionEvaluationResult.disabled("Database is not available");

468

}

469

}

470

471

private boolean isDatabaseAvailable() {

472

try {

473

java.sql.Connection conn = java.sql.DriverManager.getConnection("jdbc:h2:mem:test");

474

conn.close();

475

return true;

476

} catch (java.sql.SQLException e) {

477

return false;

478

}

479

}

480

}

481

482

@ExtendWith(DatabaseAvailableCondition.class)

483

class DatabaseTest {

484

485

@Test

486

void testDatabaseOperation() {

487

// Only runs if database is available

488

assertTrue(true);

489

}

490

}

491

```

492

493

### Exception Handling

494

495

Extensions for handling exceptions during test execution.

496

497

```java { .api }

498

/**

499

* Handle exceptions thrown during test execution

500

*/

501

interface TestExecutionExceptionHandler extends Extension {

502

void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;

503

}

504

505

/**

506

* Handle exceptions thrown during lifecycle method execution

507

*/

508

interface LifecycleMethodExecutionExceptionHandler extends Extension {

509

void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;

510

void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;

511

void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;

512

void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable throwable) throws Throwable;

513

}

514

```

515

516

**Usage Examples:**

517

518

```java

519

// Retry extension for handling flaky tests

520

public class RetryExtension implements TestExecutionExceptionHandler {

521

522

private static final int MAX_RETRIES = 3;

523

524

@Override

525

public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {

526

Namespace namespace = Namespace.create("retry");

527

Store store = context.getStore(namespace);

528

529

int attempts = store.getOrDefault("attempts", Integer.class, 0);

530

if (attempts < MAX_RETRIES && isRetryableException(throwable)) {

531

store.put("attempts", attempts + 1);

532

System.out.printf("Retrying test %s (attempt %d/%d)%n",

533

context.getDisplayName(), attempts + 1, MAX_RETRIES);

534

// Re-throw as TestAbortedException to retry

535

throw new TestAbortedException("Retrying due to: " + throwable.getMessage());

536

} else {

537

// Max retries exceeded or non-retryable exception

538

throw throwable;

539

}

540

}

541

542

private boolean isRetryableException(Throwable throwable) {

543

return throwable instanceof java.io.IOException ||

544

throwable instanceof java.net.ConnectException;

545

}

546

}

547

```

548

549

### Test Watching

550

551

Extensions for observing test execution results.

552

553

```java { .api }

554

/**

555

* Observe test execution outcomes

556

*/

557

interface TestWatcher extends Extension {

558

/**

559

* Called when a test is disabled

560

*/

561

default void testDisabled(ExtensionContext context, java.util.Optional<String> reason) {}

562

563

/**

564

* Called when a test completes successfully

565

*/

566

default void testSuccessful(ExtensionContext context) {}

567

568

/**

569

* Called when a test is aborted

570

*/

571

default void testAborted(ExtensionContext context, Throwable cause) {}

572

573

/**

574

* Called when a test fails

575

*/

576

default void testFailed(ExtensionContext context, Throwable cause) {}

577

}

578

```

579

580

**Usage Examples:**

581

582

```java

583

// Test result reporter extension

584

public class TestResultReporter implements TestWatcher {

585

586

private final List<TestResult> results = new ArrayList<>();

587

588

@Override

589

public void testSuccessful(ExtensionContext context) {

590

results.add(new TestResult(context.getDisplayName(), "PASSED", null));

591

System.out.println("✓ " + context.getDisplayName());

592

}

593

594

@Override

595

public void testFailed(ExtensionContext context, Throwable cause) {

596

results.add(new TestResult(context.getDisplayName(), "FAILED", cause.getMessage()));

597

System.out.println("✗ " + context.getDisplayName() + ": " + cause.getMessage());

598

}

599

600

@Override

601

public void testAborted(ExtensionContext context, Throwable cause) {

602

results.add(new TestResult(context.getDisplayName(), "ABORTED", cause.getMessage()));

603

System.out.println("○ " + context.getDisplayName() + ": " + cause.getMessage());

604

}

605

606

@Override

607

public void testDisabled(ExtensionContext context, java.util.Optional<String> reason) {

608

String reasonText = reason.orElse("No reason provided");

609

results.add(new TestResult(context.getDisplayName(), "DISABLED", reasonText));

610

System.out.println("- " + context.getDisplayName() + ": " + reasonText);

611

}

612

613

public java.util.List<TestResult> getResults() {

614

return java.util.Collections.unmodifiableList(results);

615

}

616

}

617

```

618

619

### Method Invocation Interception

620

621

Extensions for intercepting and modifying test method invocations.

622

623

```java { .api }

624

/**

625

* Intercept test method invocations

626

*/

627

interface InvocationInterceptor extends Extension {

628

/**

629

* Intercept test method invocation

630

*/

631

default void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<java.lang.reflect.Method> invocationContext,

632

ExtensionContext extensionContext) throws Throwable {

633

invocation.proceed();

634

}

635

636

/**

637

* Intercept test template method invocation

638

*/

639

default <T> T interceptTestTemplateMethod(Invocation<T> invocation, ReflectiveInvocationContext<java.lang.reflect.Method> invocationContext,

640

ExtensionContext extensionContext) throws Throwable {

641

return invocation.proceed();

642

}

643

644

/**

645

* Intercept test factory method invocation

646

*/

647

default <T> T interceptTestFactoryMethod(Invocation<T> invocation, ReflectiveInvocationContext<java.lang.reflect.Method> invocationContext,

648

ExtensionContext extensionContext) throws Throwable {

649

return invocation.proceed();

650

}

651

652

/**

653

* Intercept dynamic test invocation

654

*/

655

default void interceptDynamicTest(Invocation<Void> invocation, DynamicTestInvocationContext invocationContext,

656

ExtensionContext extensionContext) throws Throwable {

657

invocation.proceed();

658

}

659

}

660

```

661

662

### Extension Context

663

664

Comprehensive context information and utilities available to all extensions.

665

666

```java { .api }

667

/**

668

* Context information for extension execution

669

*/

670

interface ExtensionContext {

671

/**

672

* Get the unique ID of this context

673

*/

674

String getUniqueId();

675

676

/**

677

* Get the display name

678

*/

679

String getDisplayName();

680

681

/**

682

* Get all tags associated with this context

683

*/

684

java.util.Set<String> getTags();

685

686

/**

687

* Get the parent context (if any)

688

*/

689

java.util.Optional<ExtensionContext> getParent();

690

691

/**

692

* Get the root context

693

*/

694

ExtensionContext getRoot();

695

696

/**

697

* Get the test element (class, method, etc.)

698

*/

699

java.util.Optional<java.lang.reflect.AnnotatedElement> getElement();

700

701

/**

702

* Get the test class

703

*/

704

java.util.Optional<Class<?>> getTestClass();

705

706

/**

707

* Get the test method (if this is a method context)

708

*/

709

java.util.Optional<java.lang.reflect.Method> getTestMethod();

710

711

/**

712

* Get the test instance (if available)

713

*/

714

java.util.Optional<Object> getTestInstance();

715

716

/**

717

* Get all test instances in the hierarchy

718

*/

719

java.util.Optional<TestInstances> getTestInstances();

720

721

/**

722

* Get a namespace-scoped data store

723

*/

724

Store getStore(Namespace namespace);

725

726

/**

727

* Publish a report entry

728

*/

729

void publishReportEntry(java.util.Map<String, String> map);

730

void publishReportEntry(String key, String value);

731

732

/**

733

* Get configuration parameter value

734

*/

735

java.util.Optional<String> getConfigurationParameter(String key);

736

737

/**

738

* Get configuration parameter with type conversion

739

*/

740

<T> java.util.Optional<T> getConfigurationParameter(String key, java.util.function.Function<String, T> transformer);

741

742

/**

743

* Get the ExecutableInvoker for this context

744

* Allows dynamic invocation of methods and constructors with parameter resolution

745

*/

746

ExecutableInvoker getExecutableInvoker();

747

}

748

```

749

750

### Timeout Handling Extensions

751

752

Extensions for handling timeouts and thread interrupts.

753

754

```java { .api }

755

/**

756

* Called before thread interruption during timeout handling

757

*/

758

interface PreInterruptCallback extends Extension {

759

/**

760

* Property name for enabling thread dump on timeout

761

*/

762

String THREAD_DUMP_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.timeout.threaddump.enabled";

763

764

/**

765

* Callback invoked before a thread is interrupted

766

* @param preInterruptContext context with target thread information

767

* @param extensionContext the extension context for the callback

768

*/

769

void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) throws Exception;

770

}

771

772

/**

773

* Context information for thread interruption

774

*/

775

interface PreInterruptContext {

776

/**

777

* Get the thread that will be interrupted

778

* @return the target thread

779

*/

780

Thread getThreadToInterrupt();

781

}

782

```

783

784

**Usage Examples:**

785

786

```java

787

// Thread dump extension for timeout debugging

788

public class TimeoutDebuggingExtension implements PreInterruptCallback {

789

790

@Override

791

public void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) throws Exception {

792

Thread targetThread = preInterruptContext.getThreadToInterrupt();

793

System.err.printf("About to interrupt thread '%s' due to timeout in %s%n",

794

targetThread.getName(), extensionContext.getDisplayName());

795

796

// Dump thread stack for debugging

797

StackTraceElement[] stack = targetThread.getStackTrace();

798

System.err.println("Thread stack trace:");

799

for (StackTraceElement element : stack) {

800

System.err.println(" at " + element);

801

}

802

803

// Log additional context information

804

extensionContext.publishReportEntry("timeout.thread", targetThread.getName());

805

extensionContext.publishReportEntry("timeout.test", extensionContext.getDisplayName());

806

}

807

}

808

809

// Usage

810

@ExtendWith(TimeoutDebuggingExtension.class)

811

class TimeoutTest {

812

813

@Test

814

@Timeout(value = 1, unit = TimeUnit.SECONDS)

815

void testThatMightTimeout() {

816

// Test logic that might exceed timeout

817

while (true) {

818

// Infinite loop to demonstrate timeout

819

}

820

}

821

}

822

```

823

824

### Executable Invocation

825

826

Utility for invoking methods and constructors with parameter resolution.

827

828

```java { .api }

829

/**

830

* Allows invoking executables with dynamic parameter resolution

831

*/

832

interface ExecutableInvoker {

833

/**

834

* Invoke static method with parameter resolution

835

* @param method the method to invoke

836

* @return method result

837

*/

838

default Object invoke(java.lang.reflect.Method method) {

839

return invoke(method, null);

840

}

841

842

/**

843

* Invoke method with parameter resolution

844

* @param method the method to invoke

845

* @param target the target instance (null for static methods)

846

* @return method result

847

*/

848

Object invoke(java.lang.reflect.Method method, Object target);

849

850

/**

851

* Invoke constructor with parameter resolution

852

* @param constructor the constructor to invoke

853

* @return constructed instance

854

*/

855

default <T> T invoke(java.lang.reflect.Constructor<T> constructor) {

856

return invoke(constructor, null);

857

}

858

859

/**

860

* Invoke constructor with outer instance and parameter resolution

861

* @param constructor the constructor to invoke

862

* @param outerInstance the outer instance for inner classes

863

* @return constructed instance

864

*/

865

<T> T invoke(java.lang.reflect.Constructor<T> constructor, Object outerInstance);

866

}

867

```

868

869

**Usage Examples:**

870

871

```java

872

// Extension that demonstrates ExecutableInvoker usage

873

public class DynamicInvocationExtension implements BeforeEachCallback, ParameterResolver {

874

875

@Override

876

public void beforeEach(ExtensionContext context) throws Exception {

877

// Get the ExecutableInvoker from the context

878

ExecutableInvoker invoker = context.getExecutableInvoker();

879

880

// Dynamically invoke setup methods with parameter resolution

881

java.lang.reflect.Method[] methods = context.getRequiredTestClass().getDeclaredMethods();

882

for (java.lang.reflect.Method method : methods) {

883

if (method.isAnnotationPresent(DynamicSetup.class)) {

884

Object testInstance = context.getRequiredTestInstance();

885

invoker.invoke(method, testInstance);

886

}

887

}

888

}

889

890

@Override

891

public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {

892

return parameterContext.getParameter().getType() == ExecutableInvoker.class;

893

}

894

895

@Override

896

public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {

897

return extensionContext.getExecutableInvoker();

898

}

899

}

900

901

// Usage in tests

902

@ExtendWith(DynamicInvocationExtension.class)

903

class DynamicInvocationTest {

904

905

@DynamicSetup

906

void setupWithParameters(TestInfo testInfo, TestReporter reporter) {

907

reporter.publishEntry("setup", "Dynamic setup called for " + testInfo.getDisplayName());

908

}

909

910

@Test

911

void testWithInvoker(ExecutableInvoker invoker) throws Exception {

912

// Use invoker to dynamically call methods

913

java.lang.reflect.Method method = this.getClass().getDeclaredMethod("helperMethod", String.class);

914

Object result = invoker.invoke(method, this);

915

assertNotNull(result);

916

}

917

918

String helperMethod(String input) {

919

return "Helper: " + input;

920

}

921

}

922

```

923

924

### Extension Container Annotations

925

926

Annotations for organizing extension declarations.

927

928

```java { .api }

929

/**

930

* Container for multiple @ExtendWith declarations

931

* Optional since @ExtendWith is repeatable

932

*/

933

@Extensions({

934

@ExtendWith(DatabaseExtension.class),

935

@ExtendWith(MockitoExtension.class),

936

@ExtendWith(TimingExtension.class)

937

})

938

@interface Extensions {

939

ExtendWith[] value();

940

}

941

```

942

943

**Usage Examples:**

944

945

```java

946

// Using @Extensions container (optional)

947

@Extensions({

948

@ExtendWith(DatabaseExtension.class),

949

@ExtendWith(LoggingExtension.class),

950

@ExtendWith(MetricsExtension.class)

951

})

952

class MultiExtensionTest {

953

954

@Test

955

void testWithMultipleExtensions() {

956

// All extensions are applied

957

assertTrue(true);

958

}

959

}

960

961

// Alternative using repeatable @ExtendWith (recommended)

962

@ExtendWith(DatabaseExtension.class)

963

@ExtendWith(LoggingExtension.class)

964

@ExtendWith(MetricsExtension.class)

965

class RepeatedExtensionTest {

966

967

@Test

968

void testWithRepeatedExtensions() {

969

// Same effect as @Extensions container

970

assertTrue(true);

971

}

972

}

973

```

974

975

### Extension Context Store

976

977

Scoped data storage for sharing information between extensions and tests.

978

979

```java { .api }

980

/**

981

* Namespace-scoped key-value store for extension data

982

*/

983

interface Store {

984

/**

985

* Get value by key

986

*/

987

Object get(Object key);

988

989

/**

990

* Get typed value by key

991

*/

992

<V> V get(Object key, Class<V> requiredType);

993

994

/**

995

* Get value with default

996

*/

997

<K, V> Object getOrDefault(K key, Class<V> requiredType, V defaultValue);

998

999

/**

1000

* Put key-value pair

1001

*/

1002

void put(Object key, Object value);

1003

1004

/**

1005

* Get or compute value if absent

1006

*/

1007

Object getOrComputeIfAbsent(Object key, java.util.function.Function<Object, Object> defaultCreator);

1008

1009

/**

1010

* Get or compute typed value if absent

1011

*/

1012

<K, V> V getOrComputeIfAbsent(K key, java.util.function.Function<K, V> defaultCreator, Class<V> requiredType);

1013

1014

/**

1015

* Remove value by key

1016

*/

1017

Object remove(Object key);

1018

1019

/**

1020

* Remove typed value by key

1021

*/

1022

<V> V remove(Object key, Class<V> requiredType);

1023

}

1024

1025

/**

1026

* Namespace for scoping store data

1027

*/

1028

class Namespace {

1029

/**

1030

* Global namespace

1031

*/

1032

static final Namespace GLOBAL = Namespace.create("global");

1033

1034

/**

1035

* Create namespace from parts

1036

*/

1037

static Namespace create(Object... parts);

1038

}

1039

```

1040

1041

**Usage Examples:**

1042

1043

```java

1044

public class SharedDataExtension implements BeforeAllCallback, BeforeEachCallback {

1045

1046

private static final Namespace NAMESPACE = Namespace.create("shared", "data");

1047

1048

@Override

1049

public void beforeAll(ExtensionContext context) throws Exception {

1050

// Store class-level shared data

1051

ExpensiveResource resource = new ExpensiveResource();

1052

context.getStore(NAMESPACE).put("resource", resource);

1053

}

1054

1055

@Override

1056

public void beforeEach(ExtensionContext context) throws Exception {

1057

// Access shared data and create per-test data

1058

ExpensiveResource resource = context.getStore(NAMESPACE)

1059

.get("resource", ExpensiveResource.class);

1060

1061

TestData testData = resource.createTestData(context.getDisplayName());

1062

context.getStore(NAMESPACE).put("testData", testData);

1063

}

1064

1065

public static TestData getTestData(ExtensionContext context) {

1066

return context.getStore(NAMESPACE).get("testData", TestData.class);

1067

}

1068

}

1069

```

1070

1071

## Exception Types

1072

1073

```java { .api }

1074

/**

1075

* Exception thrown when extension configuration is invalid

1076

*/

1077

class ExtensionConfigurationException extends JUnitException {

1078

ExtensionConfigurationException(String message);

1079

ExtensionConfigurationException(String message, Throwable cause);

1080

}

1081

1082

/**

1083

* Exception thrown when parameter resolution fails

1084

*/

1085

class ParameterResolutionException extends JUnitException {

1086

ParameterResolutionException(String message);

1087

ParameterResolutionException(String message, Throwable cause);

1088

}

1089

1090

/**

1091

* Exception thrown when test instantiation fails

1092

*/

1093

class TestInstantiationException extends JUnitException {

1094

TestInstantiationException(String message);

1095

TestInstantiationException(String message, Throwable cause);

1096

}

1097

1098

/**

1099

* Exception thrown when extension context operations fail

1100

*/

1101

class ExtensionContextException extends JUnitException {

1102

ExtensionContextException(String message);

1103

ExtensionContextException(String message, Throwable cause);

1104

}

1105

```

1106

1107

## Types

1108

1109

### Test Instance Management

1110

1111

```java { .api }

1112

/**

1113

* Container for test instances in nested class hierarchies

1114

*/

1115

class TestInstances {

1116

/**

1117

* Get the innermost (leaf) test instance

1118

*/

1119

Object getInnermostInstance();

1120

1121

/**

1122

* Get all test instances from outermost to innermost

1123

*/

1124

java.util.List<Object> getAllInstances();

1125

1126

/**

1127

* Find instance of specified type

1128

*/

1129

<T> java.util.Optional<T> findInstance(Class<T> requiredType);

1130

}

1131

```

1132

1133

### Extension Support Utilities

1134

1135

```java { .api }

1136

/**

1137

* Base class for type-based parameter resolvers

1138

* @param <T> the type of the parameter supported by this ParameterResolver

1139

*/

1140

abstract class TypeBasedParameterResolver<T> implements ParameterResolver {

1141

1142

/**

1143

* Constructor that automatically discovers the supported parameter type from generic declaration

1144

*/

1145

public TypeBasedParameterResolver();

1146

1147

/**

1148

* Final implementation that checks parameter type against the discovered type

1149

*/

1150

@Override

1151

public final boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext);

1152

1153

/**

1154

* Resolve the parameter - must be implemented by subclasses

1155

* @param parameterContext the context for the parameter for which an argument should be resolved

1156

* @param extensionContext the extension context for the method

1157

* @return the resolved parameter object

1158

* @throws ParameterResolutionException if an error occurs resolving the parameter

1159

*/

1160

@Override

1161

public abstract T resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)

1162

throws ParameterResolutionException;

1163

}

1164

```