or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mdconditional-execution.mdcore-testing.mddynamic-tests.mdextensions.mdindex.mdparallel-execution.mdparameterized-tests.md

extensions.mddocs/

0

# Extensions and Lifecycle

1

2

JUnit Jupiter's powerful extension model allows customizing test behavior, dependency injection, and integration with external frameworks. Extensions provide hooks into the test lifecycle and enable sophisticated test customizations.

3

4

## Imports

5

6

```java

7

import org.junit.jupiter.api.extension.*;

8

import static org.junit.jupiter.api.Assertions.*;

9

```

10

11

## Capabilities

12

13

### Extension Registration

14

15

Register extensions declaratively or programmatically.

16

17

```java { .api }

18

/**

19

* Register extensions declaratively on test classes and methods

20

*/

21

@Target({ElementType.TYPE, ElementType.METHOD})

22

@Retention(RetentionPolicy.RUNTIME)

23

@Repeatable(ExtendWith.List.class)

24

@interface ExtendWith {

25

/**

26

* Extension classes to register

27

*/

28

Class<? extends Extension>[] value();

29

30

@Target({ElementType.TYPE, ElementType.METHOD})

31

@Retention(RetentionPolicy.RUNTIME)

32

@interface List {

33

ExtendWith[] value();

34

}

35

}

36

37

/**

38

* Register extension instance via static field

39

*/

40

@Target(ElementType.FIELD)

41

@Retention(RetentionPolicy.RUNTIME)

42

@interface RegisterExtension {

43

}

44

45

/**

46

* Base extension interface - marker interface for all extensions

47

*/

48

interface Extension {

49

}

50

```

51

52

**Usage Examples:**

53

54

```java

55

// Declarative registration

56

@ExtendWith(DatabaseExtension.class)

57

@ExtendWith(MockitoExtension.class)

58

class MyTest {

59

60

@Test

61

void testWithExtensions() {

62

// Extensions are active

63

}

64

}

65

66

// Programmatic registration

67

class MyTest {

68

69

@RegisterExtension

70

static DatabaseExtension database = new DatabaseExtension("testdb");

71

72

@RegisterExtension

73

MockServerExtension mockServer = new MockServerExtension(8080);

74

75

@Test

76

void testWithProgrammaticExtensions() {

77

// Extensions configured and active

78

}

79

}

80

```

81

82

### Lifecycle Callback Extensions

83

84

Hook into test lifecycle events.

85

86

```java { .api }

87

/**

88

* Callback before all tests in container

89

*/

90

interface BeforeAllCallback extends Extension {

91

void beforeAll(ExtensionContext context) throws Exception;

92

}

93

94

/**

95

* Callback before each test method

96

*/

97

interface BeforeEachCallback extends Extension {

98

void beforeEach(ExtensionContext context) throws Exception;

99

}

100

101

/**

102

* Callback before test method execution (after @BeforeEach)

103

*/

104

interface BeforeTestExecutionCallback extends Extension {

105

void beforeTestExecution(ExtensionContext context) throws Exception;

106

}

107

108

/**

109

* Callback after test method execution (before @AfterEach)

110

*/

111

interface AfterTestExecutionCallback extends Extension {

112

void afterTestExecution(ExtensionContext context) throws Exception;

113

}

114

115

/**

116

* Callback after each test method

117

*/

118

interface AfterEachCallback extends Extension {

119

void afterEach(ExtensionContext context) throws Exception;

120

}

121

122

/**

123

* Callback after all tests in container

124

*/

125

interface AfterAllCallback extends Extension {

126

void afterAll(ExtensionContext context) throws Exception;

127

}

128

```

129

130

**Usage Example:**

131

132

```java

133

class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {

134

135

private static final String START_TIME = "start time";

136

137

@Override

138

public void beforeTestExecution(ExtensionContext context) throws Exception {

139

getStore(context).put(START_TIME, System.currentTimeMillis());

140

}

141

142

@Override

143

public void afterTestExecution(ExtensionContext context) throws Exception {

144

Method testMethod = context.getRequiredTestMethod();

145

long startTime = getStore(context).remove(START_TIME, long.class);

146

long duration = System.currentTimeMillis() - startTime;

147

148

System.out.printf("Method [%s] took %s ms.%n", testMethod.getName(), duration);

149

}

150

151

private ExtensionContext.Store getStore(ExtensionContext context) {

152

return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestMethod()));

153

}

154

}

155

156

@ExtendWith(TimingExtension.class)

157

class TimedTest {

158

159

@Test

160

void testThatTakesTime() throws InterruptedException {

161

Thread.sleep(100);

162

assertTrue(true);

163

}

164

}

165

```

166

167

### Parameter Resolution Extensions

168

169

Inject custom parameters into test methods and constructors.

170

171

```java { .api }

172

/**

173

* Resolve parameters for test methods and constructors

174

*/

175

interface ParameterResolver extends Extension {

176

/**

177

* Check if this resolver supports the parameter

178

*/

179

boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)

180

throws ParameterResolutionException;

181

182

/**

183

* Resolve the parameter value

184

*/

185

Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)

186

throws ParameterResolutionException;

187

}

188

189

/**

190

* Parameter context information

191

*/

192

interface ParameterContext {

193

Parameter getParameter();

194

int getIndex();

195

Optional<Object> getTarget();

196

boolean isAnnotated(Class<? extends Annotation> annotationType);

197

<A extends Annotation> Optional<A> findAnnotation(Class<A> annotationType);

198

<A extends Annotation> List<A> findRepeatableAnnotations(Class<A> annotationType);

199

}

200

201

/**

202

* Type-based parameter resolver base class

203

*/

204

abstract class TypeBasedParameterResolver<T> implements ParameterResolver {

205

private final Class<T> parameterType;

206

207

protected TypeBasedParameterResolver(Class<T> parameterType) {

208

this.parameterType = parameterType;

209

}

210

211

@Override

212

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

213

return parameterType.equals(parameterContext.getParameter().getType());

214

}

215

216

@Override

217

public final Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {

218

return resolveParameter(extensionContext);

219

}

220

221

/**

222

* Resolve parameter of the supported type

223

*/

224

public abstract T resolveParameter(ExtensionContext extensionContext);

225

}

226

```

227

228

**Usage Examples:**

229

230

```java

231

class DatabaseConnectionResolver implements ParameterResolver {

232

233

@Override

234

public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {

235

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

236

}

237

238

@Override

239

public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {

240

return createDatabaseConnection();

241

}

242

243

private Connection createDatabaseConnection() {

244

// Create and return database connection

245

return DriverManager.getConnection("jdbc:h2:mem:test");

246

}

247

}

248

249

class TempDirectoryResolver extends TypeBasedParameterResolver<Path> {

250

251

public TempDirectoryResolver() {

252

super(Path.class);

253

}

254

255

@Override

256

public Path resolveParameter(ExtensionContext extensionContext) {

257

return Files.createTempDirectory("junit-test");

258

}

259

}

260

261

@ExtendWith({DatabaseConnectionResolver.class, TempDirectoryResolver.class})

262

class DatabaseTest {

263

264

@Test

265

void testWithInjectedParameters(Connection connection, Path tempDir) {

266

assertNotNull(connection);

267

assertNotNull(tempDir);

268

assertTrue(Files.exists(tempDir));

269

}

270

}

271

```

272

273

### Conditional Execution Extensions

274

275

Control when tests should be executed.

276

277

```java { .api }

278

/**

279

* Determine whether test should be executed

280

*/

281

interface ExecutionCondition extends Extension {

282

/**

283

* Evaluate execution condition

284

*/

285

ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context);

286

}

287

288

/**

289

* Result of condition evaluation

290

*/

291

class ConditionEvaluationResult {

292

/**

293

* Create enabled result

294

*/

295

static ConditionEvaluationResult enabled(String reason);

296

297

/**

298

* Create disabled result

299

*/

300

static ConditionEvaluationResult disabled(String reason);

301

302

/**

303

* Check if execution is disabled

304

*/

305

boolean isDisabled();

306

307

/**

308

* Get reason for the result

309

*/

310

Optional<String> getReason();

311

}

312

```

313

314

**Usage Example:**

315

316

```java

317

class SystemPropertyCondition implements ExecutionCondition {

318

319

@Override

320

public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {

321

Optional<SystemProperty> annotation = context.getElement()

322

.map(element -> element.getAnnotation(SystemProperty.class));

323

324

if (annotation.isPresent()) {

325

SystemProperty systemProperty = annotation.get();

326

String actualValue = System.getProperty(systemProperty.name());

327

328

if (systemProperty.value().equals(actualValue)) {

329

return ConditionEvaluationResult.enabled("System property matches");

330

} else {

331

return ConditionEvaluationResult.disabled(

332

String.format("System property [%s] does not match expected value [%s]",

333

systemProperty.name(), systemProperty.value()));

334

}

335

}

336

337

return ConditionEvaluationResult.enabled("No system property condition");

338

}

339

}

340

341

@Target({ElementType.TYPE, ElementType.METHOD})

342

@Retention(RetentionPolicy.RUNTIME)

343

@ExtendWith(SystemPropertyCondition.class)

344

@interface SystemProperty {

345

String name();

346

String value();

347

}

348

349

class ConditionalTest {

350

351

@Test

352

@SystemProperty(name = "env", value = "test")

353

void testOnlyInTestEnvironment() {

354

// Only runs when system property env=test

355

assertTrue(true);

356

}

357

}

358

```

359

360

### Test Instance Extensions

361

362

Control test instance creation and lifecycle.

363

364

```java { .api }

365

/**

366

* Create test instances

367

*/

368

interface TestInstanceFactory extends Extension {

369

/**

370

* Create test instance

371

*/

372

Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext)

373

throws TestInstantiationException;

374

}

375

376

/**

377

* Test instance factory context

378

*/

379

interface TestInstanceFactoryContext {

380

Class<?> getTestClass();

381

Optional<Object> getOuterInstance();

382

}

383

384

/**

385

* Callback before test instance construction

386

*/

387

interface TestInstancePreConstructCallback extends Extension {

388

void preConstructTestInstance(TestInstancePreConstructContext context, ExtensionContext extensionContext);

389

}

390

391

/**

392

* Process test instance after construction

393

*/

394

interface TestInstancePostProcessor extends Extension {

395

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

396

}

397

398

/**

399

* Callback before test instance destruction

400

*/

401

interface TestInstancePreDestroyCallback extends Extension {

402

void preDestroyTestInstance(ExtensionContext context) throws Exception;

403

}

404

```

405

406

**Usage Example:**

407

408

```java

409

class DependencyInjectionExtension implements TestInstancePostProcessor {

410

411

@Override

412

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

413

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

414

415

for (Field field : testClass.getDeclaredFields()) {

416

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

417

field.setAccessible(true);

418

field.set(testInstance, createDependency(field.getType()));

419

}

420

}

421

}

422

423

private Object createDependency(Class<?> type) {

424

// Create dependency instance

425

if (type == UserService.class) {

426

return new UserService();

427

}

428

return null;

429

}

430

}

431

432

@Target(ElementType.FIELD)

433

@Retention(RetentionPolicy.RUNTIME)

434

@interface Inject {

435

}

436

437

@ExtendWith(DependencyInjectionExtension.class)

438

class ServiceTest {

439

440

@Inject

441

private UserService userService;

442

443

@Test

444

void testWithInjectedService() {

445

assertNotNull(userService);

446

// Use injected service

447

}

448

}

449

```

450

451

### Exception Handling Extensions

452

453

Handle test execution exceptions.

454

455

```java { .api }

456

/**

457

* Handle exceptions thrown during test execution

458

*/

459

interface TestExecutionExceptionHandler extends Extension {

460

/**

461

* Handle test execution exception

462

*/

463

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

464

}

465

466

/**

467

* Callback before test interruption

468

*/

469

interface PreInterruptCallback extends Extension {

470

/**

471

* Called before test thread is interrupted

472

*/

473

void preInterrupt(PreInterruptContext context, ExtensionContext extensionContext) throws Exception;

474

}

475

476

/**

477

* Pre-interrupt context information

478

*/

479

interface PreInterruptContext {

480

Thread getThreadToInterrupt();

481

Optional<String> getReason();

482

}

483

484

/**

485

* Watch test execution and results

486

*/

487

interface TestWatcher extends Extension {

488

/**

489

* Called when test is disabled

490

*/

491

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

492

493

/**

494

* Called when test succeeds

495

*/

496

default void testSuccessful(ExtensionContext context) {}

497

498

/**

499

* Called when test is aborted

500

*/

501

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

502

503

/**

504

* Called when test fails

505

*/

506

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

507

}

508

509

/**

510

* Intercept method invocations

511

*/

512

interface InvocationInterceptor extends Extension {

513

/**

514

* Intercept test method invocation

515

*/

516

default void interceptTestMethod(Invocation<Void> invocation,

517

ReflectiveInvocationContext<Method> invocationContext,

518

ExtensionContext extensionContext) throws Throwable {

519

invocation.proceed();

520

}

521

522

/**

523

* Intercept test class constructor invocation

524

*/

525

default <T> T interceptTestClassConstructor(Invocation<T> invocation,

526

ReflectiveInvocationContext<Constructor<T>> invocationContext,

527

ExtensionContext extensionContext) throws Throwable {

528

return invocation.proceed();

529

}

530

531

/**

532

* Intercept BeforeAll method invocation

533

*/

534

default void interceptBeforeAllMethod(Invocation<Void> invocation,

535

ReflectiveInvocationContext<Method> invocationContext,

536

ExtensionContext extensionContext) throws Throwable {

537

invocation.proceed();

538

}

539

540

/**

541

* Intercept BeforeEach method invocation

542

*/

543

default void interceptBeforeEachMethod(Invocation<Void> invocation,

544

ReflectiveInvocationContext<Method> invocationContext,

545

ExtensionContext extensionContext) throws Throwable {

546

invocation.proceed();

547

}

548

549

/**

550

* Intercept AfterEach method invocation

551

*/

552

default void interceptAfterEachMethod(Invocation<Void> invocation,

553

ReflectiveInvocationContext<Method> invocationContext,

554

ExtensionContext extensionContext) throws Throwable {

555

invocation.proceed();

556

}

557

558

/**

559

* Intercept AfterAll method invocation

560

*/

561

default void interceptAfterAllMethod(Invocation<Void> invocation,

562

ReflectiveInvocationContext<Method> invocationContext,

563

ExtensionContext extensionContext) throws Throwable {

564

invocation.proceed();

565

}

566

}

567

```

568

569

**Usage Example:**

570

571

```java

572

class RetryExtension implements TestExecutionExceptionHandler {

573

574

@Override

575

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

576

Optional<Retry> retryAnnotation = context.getElement()

577

.map(element -> element.getAnnotation(Retry.class));

578

579

if (retryAnnotation.isPresent()) {

580

int maxRetries = retryAnnotation.get().value();

581

ExtensionContext.Store store = getStore(context);

582

int retryCount = store.getOrComputeIfAbsent("retryCount", key -> 0, Integer.class);

583

584

if (retryCount < maxRetries) {

585

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

586

// Retry the test by not re-throwing the exception

587

return;

588

}

589

}

590

591

// Re-throw if no retry or max retries reached

592

throw throwable;

593

}

594

595

private ExtensionContext.Store getStore(ExtensionContext context) {

596

return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestMethod()));

597

}

598

}

599

600

@Target(ElementType.METHOD)

601

@Retention(RetentionPolicy.RUNTIME)

602

@ExtendWith(RetryExtension.class)

603

@interface Retry {

604

int value() default 3;

605

}

606

607

class FlakeyTest {

608

609

@Test

610

@Retry(5)

611

void testThatMightFail() {

612

if (Math.random() < 0.7) {

613

throw new RuntimeException("Random failure");

614

}

615

assertTrue(true);

616

}

617

}

618

```

619

620

### Extension Context and Store

621

622

Access test context and store data across extension callbacks.

623

624

```java { .api }

625

/**

626

* Extension execution context

627

*/

628

interface ExtensionContext {

629

/**

630

* Get parent context

631

*/

632

Optional<ExtensionContext> getParent();

633

634

/**

635

* Get root context

636

*/

637

ExtensionContext getRoot();

638

639

/**

640

* Get unique ID

641

*/

642

String getUniqueId();

643

644

/**

645

* Get display name

646

*/

647

String getDisplayName();

648

649

/**

650

* Get all tags

651

*/

652

Set<String> getTags();

653

654

/**

655

* Get annotated element (class or method)

656

*/

657

Optional<AnnotatedElement> getElement();

658

659

/**

660

* Get test class

661

*/

662

Optional<Class<?>> getTestClass();

663

664

/**

665

* Get required test class (throws if not present)

666

*/

667

Class<?> getRequiredTestClass();

668

669

/**

670

* Get test instance lifecycle

671

*/

672

Optional<TestInstance.Lifecycle> getTestInstanceLifecycle();

673

674

/**

675

* Get test instance (may be null for static methods)

676

*/

677

Optional<Object> getTestInstance();

678

679

/**

680

* Get all test instances for nested tests

681

*/

682

Optional<TestInstances> getTestInstances();

683

684

/**

685

* Get test method

686

*/

687

Optional<Method> getTestMethod();

688

689

/**

690

* Get required test method (throws if not present)

691

*/

692

Method getRequiredTestMethod();

693

694

/**

695

* Get execution exception if test failed

696

*/

697

Optional<Throwable> getExecutionException();

698

699

/**

700

* Get configuration parameter

701

*/

702

Optional<String> getConfigurationParameter(String key);

703

704

/**

705

* Get store for sharing data

706

*/

707

Store getStore(Namespace namespace);

708

709

/**

710

* Publish entry to test report

711

*/

712

void publishReportEntry(Map<String, String> map);

713

void publishReportEntry(String key, String value);

714

715

/**

716

* Store namespace for organizing data

717

*/

718

class Namespace {

719

static Namespace create(Object... parts);

720

static final Namespace GLOBAL;

721

}

722

723

/**

724

* Key-value store for extension data

725

*/

726

interface Store {

727

Object get(Object key);

728

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

729

<K, V> Object getOrComputeIfAbsent(K key, Function<K, V> defaultCreator);

730

<K, V> V getOrComputeIfAbsent(K key, Function<K, V> defaultCreator, Class<V> requiredType);

731

void put(Object key, Object value);

732

Object remove(Object key);

733

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

734

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

735

void clear();

736

737

interface CloseableResource {

738

void close() throws Throwable;

739

}

740

}

741

}

742

```

743

744

**Usage Example:**

745

746

```java

747

class DataSharingExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback {

748

749

@Override

750

public void beforeAll(ExtensionContext context) throws Exception {

751

// Store shared data for all tests

752

ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.GLOBAL);

753

store.put("sharedData", new SharedTestData());

754

}

755

756

@Override

757

public void beforeEach(ExtensionContext context) throws Exception {

758

// Access test-specific information

759

String testName = context.getDisplayName();

760

Set<String> tags = context.getTags();

761

762

// Store per-test data

763

ExtensionContext.Store store = getStore(context);

764

store.put("testStartTime", System.currentTimeMillis());

765

766

System.out.printf("Starting test: %s with tags: %s%n", testName, tags);

767

}

768

769

@Override

770

public void afterAll(ExtensionContext context) throws Exception {

771

// Cleanup shared data

772

ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.GLOBAL);

773

store.remove("sharedData");

774

}

775

776

private ExtensionContext.Store getStore(ExtensionContext context) {

777

return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestMethod()));

778

}

779

}

780

```

781

782

## Additional Types

783

784

### Invocation and Context Types

785

786

Types used with InvocationInterceptor and other advanced extension features.

787

788

```java { .api }

789

/**

790

* Represents an invocation that can be proceeded

791

*/

792

interface Invocation<T> {

793

/**

794

* Proceed with the invocation

795

*/

796

T proceed() throws Throwable;

797

798

/**

799

* Skip the invocation

800

*/

801

void skip();

802

}

803

804

/**

805

* Context for reflective invocations

806

*/

807

interface ReflectiveInvocationContext<T> {

808

/**

809

* Get the executable being invoked (Constructor or Method)

810

*/

811

T getExecutable();

812

813

/**

814

* Get the arguments for the invocation

815

*/

816

List<Object> getArguments();

817

818

/**

819

* Get the target instance (null for static methods/constructors)

820

*/

821

Optional<Object> getTarget();

822

}

823

824

/**

825

* Test instances hierarchy for nested tests

826

*/

827

interface TestInstances {

828

/**

829

* Get the innermost (most nested) test instance

830

*/

831

Object getInnermostInstance();

832

833

/**

834

* Get all test instances from outermost to innermost

835

*/

836

List<Object> getEnclosingInstances();

837

838

/**

839

* Get all test instances (enclosing + innermost)

840

*/

841

List<Object> getAllInstances();

842

843

/**

844

* Find test instance of specific type

845

*/

846

<T> Optional<T> findInstance(Class<T> requiredType);

847

}

848

```