Repository: junit-team/junit-lambda
Branch: main
Commit: 2e5966b07a02
Files: 2298
Total size: 8.5 MB
Directory structure:
gitextract_rqeyty5f/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── config.yml
│ │ ├── feature_request.md
│ │ └── task.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── actions/
│ │ ├── main-build/
│ │ │ └── action.yml
│ │ ├── maven-central-user-token/
│ │ │ └── action.yml
│ │ ├── run-gradle/
│ │ │ └── action.yml
│ │ └── setup-test-jdk/
│ │ └── action.yml
│ ├── codecov.yml
│ ├── dco.yml
│ ├── renovate.json5
│ ├── scripts/
│ │ ├── checkBuildReproducibility.sh
│ │ ├── close-github-milestone.js
│ │ ├── create-github-release.js
│ │ └── waitForUrl.sh
│ ├── workflows/
│ │ ├── close-inactive-issues.yml
│ │ ├── codeql.yml
│ │ ├── cross-version.yml
│ │ ├── deploy-docs.yml
│ │ ├── gradle-dependency-submission.yml
│ │ ├── label-opened-issues.yml
│ │ ├── label-pull-request.yml
│ │ ├── lock-resolved-issues.yml
│ │ ├── main.yml
│ │ ├── ossf-scorecard.yml
│ │ ├── release.yml
│ │ ├── reproducible-build.yml
│ │ ├── sanitize-closed-issues.yml
│ │ └── zizmor-analysis.yml
│ └── zizmor.yml
├── .gitignore
├── .idea/
│ └── vcs.xml
├── .jitpack.yml
├── CONTRIBUTING.md
├── KEYS
├── LICENSE.md
├── NOTICE.md
├── README.md
├── RELEASING.md
├── SECURITY.md
├── build.gradle.kts
├── documentation/
│ ├── .tool-versions
│ ├── README.md
│ ├── antora-playbook.yml
│ ├── antora.yml
│ ├── documentation.gradle.kts
│ ├── modules/
│ │ └── ROOT/
│ │ ├── extra-site-nav.adoc
│ │ ├── images/
│ │ │ └── _source/
│ │ │ ├── extensions_BrokenLifecycleMethodConfigDemo.txt
│ │ │ ├── extensions_DatabaseTestsDemo.txt
│ │ │ └── extensions_lifecycle.docx
│ │ ├── nav.adoc
│ │ ├── pages/
│ │ │ ├── advanced-topics/
│ │ │ │ ├── engines.adoc
│ │ │ │ ├── junit-platform-reporting.adoc
│ │ │ │ ├── junit-platform-suite-engine.adoc
│ │ │ │ ├── launcher-api.adoc
│ │ │ │ └── testkit.adoc
│ │ │ ├── api-evolution.adoc
│ │ │ ├── appendix.adoc
│ │ │ ├── extensions/
│ │ │ │ ├── conditional-test-execution.adoc
│ │ │ │ ├── exception-handling.adoc
│ │ │ │ ├── intercepting-invocations.adoc
│ │ │ │ ├── keeping-state-in-extensions.adoc
│ │ │ │ ├── overview.adoc
│ │ │ │ ├── parameter-resolution.adoc
│ │ │ │ ├── pre-interrupt-callback.adoc
│ │ │ │ ├── providing-invocation-contexts-for-class-templates.adoc
│ │ │ │ ├── providing-invocation-contexts-for-test-templates.adoc
│ │ │ │ ├── registering-extensions.adoc
│ │ │ │ ├── relative-execution-order-of-user-code-and-extensions.adoc
│ │ │ │ ├── supported-utilities-in-extensions.adoc
│ │ │ │ ├── test-instance-factories.adoc
│ │ │ │ ├── test-instance-post-processing.adoc
│ │ │ │ ├── test-instance-pre-construct-callback.adoc
│ │ │ │ ├── test-instance-pre-destroy-callback.adoc
│ │ │ │ ├── test-lifecycle-callbacks.adoc
│ │ │ │ └── test-result-processing.adoc
│ │ │ ├── migrating-from-junit4.adoc
│ │ │ ├── overview.adoc
│ │ │ ├── release-notes.adoc
│ │ │ ├── running-tests/
│ │ │ │ ├── build-support.adoc
│ │ │ │ ├── capturing-standard-output-error.adoc
│ │ │ │ ├── configuration-parameters.adoc
│ │ │ │ ├── console-launcher.adoc
│ │ │ │ ├── discovery-issues.adoc
│ │ │ │ ├── discovery-selectors.adoc
│ │ │ │ ├── ide-support.adoc
│ │ │ │ ├── intro.adoc
│ │ │ │ ├── source-launcher.adoc
│ │ │ │ ├── stack-trace-pruning.adoc
│ │ │ │ ├── tags.adoc
│ │ │ │ └── using-listeners-and-interceptors.adoc
│ │ │ └── writing-tests/
│ │ │ ├── annotations.adoc
│ │ │ ├── assertions.adoc
│ │ │ ├── assumptions.adoc
│ │ │ ├── built-in-extensions.adoc
│ │ │ ├── class-templates.adoc
│ │ │ ├── conditional-test-execution.adoc
│ │ │ ├── definitions.adoc
│ │ │ ├── dependency-injection-for-constructors-and-methods.adoc
│ │ │ ├── disabling-tests.adoc
│ │ │ ├── display-names.adoc
│ │ │ ├── dynamic-tests.adoc
│ │ │ ├── exception-handling.adoc
│ │ │ ├── intro.adoc
│ │ │ ├── nested-tests.adoc
│ │ │ ├── parallel-execution.adoc
│ │ │ ├── parameterized-classes-and-tests.adoc
│ │ │ ├── repeated-tests.adoc
│ │ │ ├── tagging-and-filtering.adoc
│ │ │ ├── test-classes-and-methods.adoc
│ │ │ ├── test-execution-order.adoc
│ │ │ ├── test-instance-lifecycle.adoc
│ │ │ ├── test-interfaces-and-default-methods.adoc
│ │ │ ├── test-templates.adoc
│ │ │ └── timeouts.adoc
│ │ └── partials/
│ │ └── release-notes/
│ │ ├── release-notes-6.0.0.adoc
│ │ ├── release-notes-6.0.1.adoc
│ │ ├── release-notes-6.0.2.adoc
│ │ ├── release-notes-6.0.3.adoc
│ │ ├── release-notes-6.1.0-M1.adoc
│ │ ├── release-notes-6.1.0-RC1.adoc
│ │ ├── release-notes-6.1.0.adoc
│ │ └── release-notes-TEMPLATE.adoc
│ ├── package.json
│ └── src/
│ ├── javadoc/
│ │ ├── junit-overview.html
│ │ └── junit-stylesheet.css
│ ├── main/
│ │ └── java/
│ │ └── example/
│ │ ├── domain/
│ │ │ ├── Person.java
│ │ │ └── package-info.java
│ │ ├── registration/
│ │ │ ├── WebClient.java
│ │ │ ├── WebResponse.java
│ │ │ ├── WebServerExtension.java
│ │ │ └── package-info.java
│ │ └── util/
│ │ ├── Calculator.java
│ │ ├── ListWriter.java
│ │ ├── StringUtils.java
│ │ └── package-info.java
│ ├── plantuml/
│ │ ├── component-diagram.puml
│ │ ├── junit-platform-suite-engine-diagram.puml
│ │ ├── junit-platform-suite-engine-duplicate-test-execution-diagram.puml
│ │ └── launcher-api-diagram.puml
│ ├── test/
│ │ ├── java/
│ │ │ ├── example/
│ │ │ │ ├── AssertJAssertionsDemo.java
│ │ │ │ ├── AssertionsDemo.java
│ │ │ │ ├── AssumptionsDemo.java
│ │ │ │ ├── AutoCloseDemo.java
│ │ │ │ ├── BeforeAndAfterSuiteDemo.java
│ │ │ │ ├── ClassTemplateDemo.java
│ │ │ │ ├── ConditionalTestExecutionDemo.java
│ │ │ │ ├── CustomLauncherInterceptor.java
│ │ │ │ ├── CustomTestEngine.java
│ │ │ │ ├── DefaultLocaleTimezoneExtensionDemo.java
│ │ │ │ ├── DisabledClassDemo.java
│ │ │ │ ├── DisabledTestsDemo.java
│ │ │ │ ├── DisplayNameDemo.java
│ │ │ │ ├── DisplayNameGeneratorDemo.java
│ │ │ │ ├── DocumentationTestSuite.java
│ │ │ │ ├── DynamicTestsDemo.java
│ │ │ │ ├── DynamicTestsNamedDemo.java
│ │ │ │ ├── ExampleTestCase.java
│ │ │ │ ├── ExplicitExecutionModeDemo.java
│ │ │ │ ├── ExternalCustomConditionDemo.java
│ │ │ │ ├── ExternalFieldSourceDemo.java
│ │ │ │ ├── ExternalMethodSourceDemo.java
│ │ │ │ ├── Fast.java
│ │ │ │ ├── FastTest.java
│ │ │ │ ├── FirstCustomEngine.java
│ │ │ │ ├── HttpServerDemo.java
│ │ │ │ ├── IgnoredTestsDemo.java
│ │ │ │ ├── JUnit4Tests.java
│ │ │ │ ├── MethodSourceParameterResolutionDemo.java
│ │ │ │ ├── MyFirstJUnitJupiterRecordTests.java
│ │ │ │ ├── MyFirstJUnitJupiterTests.java
│ │ │ │ ├── MyRandomParametersTest.java
│ │ │ │ ├── OrderedNestedTestClassesDemo.java
│ │ │ │ ├── OrderedTestsDemo.java
│ │ │ │ ├── ParameterizedClassDemo.java
│ │ │ │ ├── ParameterizedLifecycleDemo.java
│ │ │ │ ├── ParameterizedMigrationDemo.java
│ │ │ │ ├── ParameterizedRecordDemo.java
│ │ │ │ ├── ParameterizedTestDemo.java
│ │ │ │ ├── PollingTimeoutDemo.java
│ │ │ │ ├── RepeatedTestsDemo.java
│ │ │ │ ├── SecondCustomEngine.java
│ │ │ │ ├── SlowTests.java
│ │ │ │ ├── StandardTests.java
│ │ │ │ ├── SuiteDemo.java
│ │ │ │ ├── SystemPropertyExtensionDemo.java
│ │ │ │ ├── TaggingDemo.java
│ │ │ │ ├── TempDirectoryDemo.java
│ │ │ │ ├── TestInfoDemo.java
│ │ │ │ ├── TestReporterDemo.java
│ │ │ │ ├── TestTemplateDemo.java
│ │ │ │ ├── TestingAStackDemo.java
│ │ │ │ ├── TimeoutDemo.java
│ │ │ │ ├── UsingTheLauncherDemo.java
│ │ │ │ ├── UsingTheLauncherForDiscoveryDemo.java
│ │ │ │ ├── callbacks/
│ │ │ │ │ ├── AbstractDatabaseTests.java
│ │ │ │ │ ├── BrokenLifecycleMethodConfigDemo.java
│ │ │ │ │ ├── DatabaseTestsDemo.java
│ │ │ │ │ ├── Extension1.java
│ │ │ │ │ ├── Extension2.java
│ │ │ │ │ ├── Logger.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── defaultmethods/
│ │ │ │ │ ├── ComparableContract.java
│ │ │ │ │ ├── EqualsContract.java
│ │ │ │ │ ├── StringTests.java
│ │ │ │ │ ├── Testable.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── exception/
│ │ │ │ │ ├── AssertDoesNotThrowExceptionDemo.java
│ │ │ │ │ ├── ExceptionAssertionDemo.java
│ │ │ │ │ ├── ExceptionAssertionExactDemo.java
│ │ │ │ │ ├── FailedAssertionDemo.java
│ │ │ │ │ ├── IgnoreIOExceptionExtension.java
│ │ │ │ │ ├── IgnoreIOExceptionTests.java
│ │ │ │ │ ├── MultipleHandlersTestCase.java
│ │ │ │ │ ├── RecordStateOnErrorExtension.java
│ │ │ │ │ ├── UncaughtExceptionHandlingDemo.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── extensions/
│ │ │ │ │ ├── HttpServerExtension.java
│ │ │ │ │ ├── HttpServerResource.java
│ │ │ │ │ ├── ParameterResolverConflictDemo.java
│ │ │ │ │ ├── ParameterResolverCustomAnnotationDemo.java
│ │ │ │ │ ├── ParameterResolverCustomTypeDemo.java
│ │ │ │ │ ├── ParameterResolverNoConflictDemo.java
│ │ │ │ │ ├── Random.java
│ │ │ │ │ ├── RandomNumberDemo.java
│ │ │ │ │ ├── RandomNumberExtension.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── interceptor/
│ │ │ │ │ ├── SwingEdtInterceptor.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── package-info.java
│ │ │ │ ├── registration/
│ │ │ │ │ ├── DocumentationDemo.java
│ │ │ │ │ └── WebServerDemo.java
│ │ │ │ ├── session/
│ │ │ │ │ ├── CloseableHttpServer.java
│ │ │ │ │ ├── GlobalSetupTeardownListener.java
│ │ │ │ │ ├── HttpTests.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── sharedresources/
│ │ │ │ │ ├── ChildrenSharedResourcesDemo.java
│ │ │ │ │ ├── DynamicSharedResourcesDemo.java
│ │ │ │ │ ├── SharedResourceDemo.java
│ │ │ │ │ ├── StaticSharedResourcesDemo.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── testinterface/
│ │ │ │ │ ├── TestInterfaceDemo.java
│ │ │ │ │ ├── TestInterfaceDynamicTestsDemo.java
│ │ │ │ │ ├── TestLifecycleLogger.java
│ │ │ │ │ ├── TimeExecutionLogger.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── testkit/
│ │ │ │ │ ├── EngineTestKitAllEventsDemo.java
│ │ │ │ │ ├── EngineTestKitDiscoveryDemo.java
│ │ │ │ │ ├── EngineTestKitFailedMethodDemo.java
│ │ │ │ │ ├── EngineTestKitSkippedMethodDemo.java
│ │ │ │ │ ├── EngineTestKitStatisticsDemo.java
│ │ │ │ │ └── package-info.java
│ │ │ │ └── timing/
│ │ │ │ ├── TimingExtension.java
│ │ │ │ ├── TimingExtensionTests.java
│ │ │ │ └── package-info.java
│ │ │ └── extensions/
│ │ │ ├── DisabledOnOpenJ9.java
│ │ │ ├── ExpectToFail.java
│ │ │ └── package-info.java
│ │ ├── kotlin/
│ │ │ └── example/
│ │ │ ├── FibonacciCalculator.kt
│ │ │ ├── KotlinAssertionsDemo.kt
│ │ │ ├── KotlinCoroutinesDemo.kt
│ │ │ ├── KotlinCoroutinesRunTestDemo.kt
│ │ │ ├── kotlin/
│ │ │ │ ├── AssumptionsDemo.kt
│ │ │ │ ├── AutoCloseDemo.kt
│ │ │ │ ├── ConditionalTestExecutionDemo.kt
│ │ │ │ ├── DefaultLocaleTimezoneExtensionDemo.kt
│ │ │ │ ├── DisplayNameDemo.kt
│ │ │ │ ├── DisplayNameGeneratorDemo.kt
│ │ │ │ ├── DynamicTestsDemo.kt
│ │ │ │ ├── DynamicTestsNamedDemo.kt
│ │ │ │ ├── ExternalCustomConditionDemo.kt
│ │ │ │ ├── Fast.kt
│ │ │ │ ├── FastTest.kt
│ │ │ │ ├── HttpServerDemo.kt
│ │ │ │ ├── MyFirstJUnitJupiterTests.kt
│ │ │ │ ├── MyRandomParametersTest.kt
│ │ │ │ ├── ParameterizedClassDemo.kt
│ │ │ │ ├── PollingTimeoutDemo.kt
│ │ │ │ ├── RepeatedTestsDemo.kt
│ │ │ │ ├── StandardTests.kt
│ │ │ │ ├── SystemPropertyExtensionDemo.kt
│ │ │ │ ├── TempDirectoryDemo.kt
│ │ │ │ ├── TestInfoDemo.kt
│ │ │ │ ├── TestReporterDemo.kt
│ │ │ │ ├── TestingAStackDemo.kt
│ │ │ │ ├── TimeoutDemo.kt
│ │ │ │ ├── defaultmethods/
│ │ │ │ │ ├── ComparableContract.kt
│ │ │ │ │ ├── EqualsContract.kt
│ │ │ │ │ ├── StringTests.kt
│ │ │ │ │ └── Testable.kt
│ │ │ │ ├── exception/
│ │ │ │ │ ├── AssertDoesNotThrowExceptionDemo.kt
│ │ │ │ │ ├── ExceptionAssertionDemo.kt
│ │ │ │ │ ├── ExceptionAssertionExactDemo.kt
│ │ │ │ │ ├── FailedAssertionDemo.kt
│ │ │ │ │ └── UncaughtExceptionHandlingDemo.kt
│ │ │ │ ├── extensions/
│ │ │ │ │ ├── HttpServerExtension.kt
│ │ │ │ │ ├── HttpServerResource.kt
│ │ │ │ │ ├── ParameterResolverConflictDemo.kt
│ │ │ │ │ ├── ParameterResolverCustomAnnotationDemo.kt
│ │ │ │ │ ├── ParameterResolverCustomTypeDemo.kt
│ │ │ │ │ ├── ParameterResolverNoConflictDemo.kt
│ │ │ │ │ ├── Random.kt
│ │ │ │ │ ├── RandomNumberDemo.kt
│ │ │ │ │ └── RandomNumberExtension.kt
│ │ │ │ ├── registration/
│ │ │ │ │ ├── DocumentationDemo.kt
│ │ │ │ │ └── DocumentationExtension.kt
│ │ │ │ ├── testinterface/
│ │ │ │ │ ├── TestInterfaceDemo.kt
│ │ │ │ │ ├── TestInterfaceDynamicTestsDemo.kt
│ │ │ │ │ ├── TestLifecycleLogger.kt
│ │ │ │ │ └── TimeExecutionLogger.kt
│ │ │ │ └── timing/
│ │ │ │ ├── TimingExtension.kt
│ │ │ │ └── TimingExtensionTests.kt
│ │ │ └── registration/
│ │ │ └── KotlinWebServerDemo.kt
│ │ └── resources/
│ │ ├── META-INF/
│ │ │ └── services/
│ │ │ └── org.junit.platform.launcher.LauncherSessionListener
│ │ ├── junit-platform.properties
│ │ ├── log4j2-test.xml
│ │ └── two-column.csv
│ └── tools/
│ └── java/
│ └── org/
│ └── junit/
│ └── api/
│ └── tools/
│ ├── AbstractApiReportWriter.java
│ ├── ApiReport.java
│ ├── ApiReportGenerator.java
│ ├── ApiReportWriter.java
│ ├── AsciidocApiReportWriter.java
│ ├── Declaration.java
│ ├── HtmlApiReportWriter.java
│ ├── MarkdownApiReportWriter.java
│ └── package-info.java
├── gradle/
│ ├── base/
│ │ ├── code-generator-model/
│ │ │ ├── .gitignore
│ │ │ ├── build.gradle.kts
│ │ │ └── src/
│ │ │ └── main/
│ │ │ ├── kotlin/
│ │ │ │ └── junitbuild/
│ │ │ │ └── generator/
│ │ │ │ └── model/
│ │ │ │ └── JRE.kt
│ │ │ └── resources/
│ │ │ └── jre.yaml
│ │ ├── dsl-extensions/
│ │ │ ├── .gitignore
│ │ │ ├── build.gradle.kts
│ │ │ └── src/
│ │ │ └── main/
│ │ │ └── kotlin/
│ │ │ └── junitbuild/
│ │ │ └── extensions/
│ │ │ ├── DependencyExtensions.kt
│ │ │ ├── ProjectExtensions.kt
│ │ │ ├── StringExtensions.kt
│ │ │ ├── TaskExtensions.kt
│ │ │ ├── VersionExtensions.kt
│ │ │ └── junitbuild.dsl-extensions.gradle.kts
│ │ ├── gradle.properties
│ │ └── settings.gradle.kts
│ ├── config/
│ │ ├── checkstyle/
│ │ │ ├── checkstyleMain.xml
│ │ │ ├── checkstyleNohttp.xml
│ │ │ ├── checkstyleTest.xml
│ │ │ └── suppressions.xml
│ │ ├── eclipse/
│ │ │ ├── junit-eclipse-formatter-settings.xml
│ │ │ └── junit-eclipse.importorder
│ │ ├── roseau/
│ │ │ └── config.yaml
│ │ └── spotless/
│ │ └── eclipse-public-license-2.0.java
│ ├── gradle-daemon-jvm.properties
│ ├── libs.versions.toml
│ ├── plugins/
│ │ ├── antora/
│ │ │ ├── build.gradle.kts
│ │ │ └── src/
│ │ │ └── main/
│ │ │ └── kotlin/
│ │ │ ├── junitbuild/
│ │ │ │ └── antora/
│ │ │ │ └── AntoraConfiguration.kt
│ │ │ └── junitbuild.antora-conventions.gradle.kts
│ │ ├── backward-compatibility/
│ │ │ ├── build.gradle.kts
│ │ │ └── src/
│ │ │ └── main/
│ │ │ └── kotlin/
│ │ │ ├── junitbuild/
│ │ │ │ └── compatibility/
│ │ │ │ ├── BackwardCompatibilityChecksExtension.kt
│ │ │ │ └── roseau/
│ │ │ │ └── RoseauDiff.kt
│ │ │ └── junitbuild.backward-compatibility.gradle.kts
│ │ ├── build-parameters/
│ │ │ └── build.gradle.kts
│ │ ├── code-generator/
│ │ │ ├── build.gradle.kts
│ │ │ └── src/
│ │ │ └── main/
│ │ │ └── kotlin/
│ │ │ ├── junitbuild/
│ │ │ │ └── generator/
│ │ │ │ └── GenerateJreRelatedSourceCode.kt
│ │ │ └── junitbuild.code-generator.gradle.kts
│ │ ├── common/
│ │ │ ├── build.gradle.kts
│ │ │ └── src/
│ │ │ └── main/
│ │ │ └── kotlin/
│ │ │ ├── JavaLibraryExtension.kt
│ │ │ ├── License.kt
│ │ │ ├── junitbuild/
│ │ │ │ ├── deps/
│ │ │ │ │ └── ByteBuddyAlignmentRule.kt
│ │ │ │ ├── eclipse/
│ │ │ │ │ └── EclipseConventionsExtension.kt
│ │ │ │ ├── exec/
│ │ │ │ │ ├── CaptureJavaExecOutput.kt
│ │ │ │ │ ├── ClasspathSystemPropertyProvider.kt
│ │ │ │ │ ├── GenerateStandaloneConsoleLauncherShadowedArtifactsFile.kt
│ │ │ │ │ └── RunConsoleLauncher.kt
│ │ │ │ ├── graalvm/
│ │ │ │ │ └── NativeImagePropertiesExtension.kt
│ │ │ │ ├── java/
│ │ │ │ │ ├── UpdateJarAction.kt
│ │ │ │ │ └── WriteArtifactsFile.kt
│ │ │ │ └── javadoc/
│ │ │ │ ├── JavadocConventionsExtension.kt
│ │ │ │ ├── JavadocValuesOption.kt
│ │ │ │ ├── ModuleSpecificJavadocFileOption.kt
│ │ │ │ └── VersionNumber.kt
│ │ │ ├── junitbuild.base-conventions.gradle.kts
│ │ │ ├── junitbuild.build-metadata.gradle.kts
│ │ │ ├── junitbuild.checkstyle-conventions.gradle.kts
│ │ │ ├── junitbuild.checkstyle-nohttp.gradle.kts
│ │ │ ├── junitbuild.eclipse-conventions.gradle.kts
│ │ │ ├── junitbuild.jacoco-aggregation-conventions.gradle.kts
│ │ │ ├── junitbuild.jacoco-conventions.gradle.kts
│ │ │ ├── junitbuild.jacoco-java-conventions.gradle.kts
│ │ │ ├── junitbuild.java-aggregator-conventions.gradle.kts
│ │ │ ├── junitbuild.java-errorprone-conventions.gradle.kts
│ │ │ ├── junitbuild.java-library-conventions.gradle.kts
│ │ │ ├── junitbuild.java-toolchain-conventions.gradle.kts
│ │ │ ├── junitbuild.javadoc-conventions.gradle.kts
│ │ │ ├── junitbuild.jmh-conventions.gradle.kts
│ │ │ ├── junitbuild.junit4-compatibility.gradle.kts
│ │ │ ├── junitbuild.kotlin-library-conventions.gradle.kts
│ │ │ ├── junitbuild.osgi-conventions.gradle.kts
│ │ │ ├── junitbuild.publishing-conventions.gradle.kts
│ │ │ ├── junitbuild.settings-conventions.settings.gradle.kts
│ │ │ ├── junitbuild.shadow-conventions.gradle.kts
│ │ │ ├── junitbuild.spotless-conventions.gradle.kts
│ │ │ └── junitbuild.testing-conventions.gradle.kts
│ │ ├── publishing/
│ │ │ ├── build.gradle.kts
│ │ │ └── src/
│ │ │ └── main/
│ │ │ └── kotlin/
│ │ │ ├── junitbuild/
│ │ │ │ └── release/
│ │ │ │ └── VerifyBinaryArtifactsAreIdentical.kt
│ │ │ ├── junitbuild.maven-central-publishing.settings.gradle.kts
│ │ │ └── junitbuild.temp-maven-repo.gradle.kts
│ │ └── settings.gradle.kts
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── junit-bom/
│ ├── README.md
│ └── junit-bom.gradle.kts
├── junit-jupiter/
│ ├── junit-jupiter.gradle.kts
│ └── src/
│ └── main/
│ └── java/
│ └── module-info.java
├── junit-jupiter-api/
│ ├── junit-jupiter-api.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── module-info.java
│ │ │ └── org/
│ │ │ └── junit/
│ │ │ └── jupiter/
│ │ │ └── api/
│ │ │ ├── AfterAll.java
│ │ │ ├── AfterEach.java
│ │ │ ├── AssertAll.java
│ │ │ ├── AssertArrayEquals.java
│ │ │ ├── AssertDoesNotThrow.java
│ │ │ ├── AssertEquals.java
│ │ │ ├── AssertFalse.java
│ │ │ ├── AssertInstanceOf.java
│ │ │ ├── AssertIterableEquals.java
│ │ │ ├── AssertLinesMatch.java
│ │ │ ├── AssertNotEquals.java
│ │ │ ├── AssertNotNull.java
│ │ │ ├── AssertNotSame.java
│ │ │ ├── AssertNull.java
│ │ │ ├── AssertSame.java
│ │ │ ├── AssertThrows.java
│ │ │ ├── AssertThrowsExactly.java
│ │ │ ├── AssertTimeout.java
│ │ │ ├── AssertTimeoutPreemptively.java
│ │ │ ├── AssertTrue.java
│ │ │ ├── AssertionFailureBuilder.java
│ │ │ ├── AssertionUtils.java
│ │ │ ├── Assertions.java
│ │ │ ├── Assumptions.java
│ │ │ ├── AutoClose.java
│ │ │ ├── BeforeAll.java
│ │ │ ├── BeforeEach.java
│ │ │ ├── ClassDescriptor.java
│ │ │ ├── ClassOrderer.java
│ │ │ ├── ClassOrdererContext.java
│ │ │ ├── ClassTemplate.java
│ │ │ ├── Constants.java
│ │ │ ├── Disabled.java
│ │ │ ├── DisplayName.java
│ │ │ ├── DisplayNameGeneration.java
│ │ │ ├── DisplayNameGenerator.java
│ │ │ ├── DynamicContainer.java
│ │ │ ├── DynamicNode.java
│ │ │ ├── DynamicTest.java
│ │ │ ├── IndicativeSentencesGeneration.java
│ │ │ ├── MediaType.java
│ │ │ ├── MethodDescriptor.java
│ │ │ ├── MethodOrderer.java
│ │ │ ├── MethodOrdererContext.java
│ │ │ ├── Named.java
│ │ │ ├── NamedExecutable.java
│ │ │ ├── Nested.java
│ │ │ ├── Order.java
│ │ │ ├── RandomOrdererUtils.java
│ │ │ ├── RepeatedTest.java
│ │ │ ├── RepetitionInfo.java
│ │ │ ├── Tag.java
│ │ │ ├── Tags.java
│ │ │ ├── Test.java
│ │ │ ├── TestClassOrder.java
│ │ │ ├── TestFactory.java
│ │ │ ├── TestInfo.java
│ │ │ ├── TestInstance.java
│ │ │ ├── TestMethodOrder.java
│ │ │ ├── TestReporter.java
│ │ │ ├── TestTemplate.java
│ │ │ ├── Timeout.java
│ │ │ ├── condition/
│ │ │ │ ├── AbstractJreCondition.java
│ │ │ │ ├── AbstractJreRangeCondition.java
│ │ │ │ ├── AbstractOsBasedExecutionCondition.java
│ │ │ │ ├── AbstractRepeatableAnnotationCondition.java
│ │ │ │ ├── BooleanExecutionCondition.java
│ │ │ │ ├── DisabledForJreRange.java
│ │ │ │ ├── DisabledForJreRangeCondition.java
│ │ │ │ ├── DisabledIf.java
│ │ │ │ ├── DisabledIfCondition.java
│ │ │ │ ├── DisabledIfEnvironmentVariable.java
│ │ │ │ ├── DisabledIfEnvironmentVariableCondition.java
│ │ │ │ ├── DisabledIfEnvironmentVariables.java
│ │ │ │ ├── DisabledIfSystemProperties.java
│ │ │ │ ├── DisabledIfSystemProperty.java
│ │ │ │ ├── DisabledIfSystemPropertyCondition.java
│ │ │ │ ├── DisabledInNativeImage.java
│ │ │ │ ├── DisabledOnJre.java
│ │ │ │ ├── DisabledOnJreCondition.java
│ │ │ │ ├── DisabledOnOs.java
│ │ │ │ ├── DisabledOnOsCondition.java
│ │ │ │ ├── EnabledForJreRange.java
│ │ │ │ ├── EnabledForJreRangeCondition.java
│ │ │ │ ├── EnabledIf.java
│ │ │ │ ├── EnabledIfCondition.java
│ │ │ │ ├── EnabledIfEnvironmentVariable.java
│ │ │ │ ├── EnabledIfEnvironmentVariableCondition.java
│ │ │ │ ├── EnabledIfEnvironmentVariables.java
│ │ │ │ ├── EnabledIfSystemProperties.java
│ │ │ │ ├── EnabledIfSystemProperty.java
│ │ │ │ ├── EnabledIfSystemPropertyCondition.java
│ │ │ │ ├── EnabledInNativeImage.java
│ │ │ │ ├── EnabledOnJre.java
│ │ │ │ ├── EnabledOnJreCondition.java
│ │ │ │ ├── EnabledOnOs.java
│ │ │ │ ├── EnabledOnOsCondition.java
│ │ │ │ ├── MethodBasedCondition.java
│ │ │ │ ├── OS.java
│ │ │ │ └── package-info.java
│ │ │ ├── extension/
│ │ │ │ ├── AfterAllCallback.java
│ │ │ │ ├── AfterClassTemplateInvocationCallback.java
│ │ │ │ ├── AfterEachCallback.java
│ │ │ │ ├── AfterTestExecutionCallback.java
│ │ │ │ ├── AnnotatedElementContext.java
│ │ │ │ ├── BeforeAllCallback.java
│ │ │ │ ├── BeforeClassTemplateInvocationCallback.java
│ │ │ │ ├── BeforeEachCallback.java
│ │ │ │ ├── BeforeTestExecutionCallback.java
│ │ │ │ ├── ClassTemplateInvocationContext.java
│ │ │ │ ├── ClassTemplateInvocationContextProvider.java
│ │ │ │ ├── ClassTemplateInvocationLifecycleMethod.java
│ │ │ │ ├── ConditionEvaluationResult.java
│ │ │ │ ├── DynamicTestInvocationContext.java
│ │ │ │ ├── ExecutableInvoker.java
│ │ │ │ ├── ExecutionCondition.java
│ │ │ │ ├── ExtendWith.java
│ │ │ │ ├── Extension.java
│ │ │ │ ├── ExtensionConfigurationException.java
│ │ │ │ ├── ExtensionContext.java
│ │ │ │ ├── ExtensionContextException.java
│ │ │ │ ├── Extensions.java
│ │ │ │ ├── InvocationInterceptor.java
│ │ │ │ ├── LifecycleMethodExecutionExceptionHandler.java
│ │ │ │ ├── MediaType.java
│ │ │ │ ├── ParameterContext.java
│ │ │ │ ├── ParameterResolutionException.java
│ │ │ │ ├── ParameterResolver.java
│ │ │ │ ├── PreInterruptCallback.java
│ │ │ │ ├── PreInterruptContext.java
│ │ │ │ ├── ReflectiveInvocationContext.java
│ │ │ │ ├── RegisterExtension.java
│ │ │ │ ├── TemplateInvocationValidationException.java
│ │ │ │ ├── TestExecutionExceptionHandler.java
│ │ │ │ ├── TestInstanceFactory.java
│ │ │ │ ├── TestInstanceFactoryContext.java
│ │ │ │ ├── TestInstancePostProcessor.java
│ │ │ │ ├── TestInstancePreConstructCallback.java
│ │ │ │ ├── TestInstancePreDestroyCallback.java
│ │ │ │ ├── TestInstances.java
│ │ │ │ ├── TestInstantiationAwareExtension.java
│ │ │ │ ├── TestInstantiationException.java
│ │ │ │ ├── TestTemplateInvocationContext.java
│ │ │ │ ├── TestTemplateInvocationContextProvider.java
│ │ │ │ ├── TestWatcher.java
│ │ │ │ ├── package-info.java
│ │ │ │ └── support/
│ │ │ │ ├── TypeBasedParameterResolver.java
│ │ │ │ └── package-info.java
│ │ │ ├── function/
│ │ │ │ ├── Executable.java
│ │ │ │ ├── ThrowingConsumer.java
│ │ │ │ ├── ThrowingSupplier.java
│ │ │ │ └── package-info.java
│ │ │ ├── io/
│ │ │ │ ├── CleanupMode.java
│ │ │ │ ├── DefaultDeletionResult.java
│ │ │ │ ├── TempDir.java
│ │ │ │ ├── TempDirDeletionStrategy.java
│ │ │ │ ├── TempDirFactory.java
│ │ │ │ └── package-info.java
│ │ │ ├── package-info.java
│ │ │ ├── parallel/
│ │ │ │ ├── Execution.java
│ │ │ │ ├── ExecutionMode.java
│ │ │ │ ├── Isolated.java
│ │ │ │ ├── ResourceAccessMode.java
│ │ │ │ ├── ResourceLock.java
│ │ │ │ ├── ResourceLockTarget.java
│ │ │ │ ├── ResourceLocks.java
│ │ │ │ ├── ResourceLocksProvider.java
│ │ │ │ ├── Resources.java
│ │ │ │ └── package-info.java
│ │ │ ├── timeout/
│ │ │ │ ├── PreemptiveTimeoutUtils.java
│ │ │ │ └── package-info.java
│ │ │ └── util/
│ │ │ ├── ClearSystemProperty.java
│ │ │ ├── DefaultLocale.java
│ │ │ ├── DefaultLocaleExtension.java
│ │ │ ├── DefaultTimeZone.java
│ │ │ ├── DefaultTimeZoneExtension.java
│ │ │ ├── JupiterLocaleUtils.java
│ │ │ ├── JupiterPropertyUtils.java
│ │ │ ├── LocaleProvider.java
│ │ │ ├── NullLocaleProvider.java
│ │ │ ├── NullTimeZoneProvider.java
│ │ │ ├── ReadsDefaultLocale.java
│ │ │ ├── ReadsDefaultTimeZone.java
│ │ │ ├── ReadsSystemProperty.java
│ │ │ ├── RestoreSystemProperties.java
│ │ │ ├── SetSystemProperty.java
│ │ │ ├── SystemPropertiesExtension.java
│ │ │ ├── TimeZoneProvider.java
│ │ │ ├── WritesDefaultLocale.java
│ │ │ ├── WritesDefaultTimeZone.java
│ │ │ ├── WritesSystemProperty.java
│ │ │ └── package-info.java
│ │ └── kotlin/
│ │ └── org/
│ │ └── junit/
│ │ └── jupiter/
│ │ └── api/
│ │ └── Assertions.kt
│ ├── templates/
│ │ └── resources/
│ │ ├── main/
│ │ │ └── org/
│ │ │ └── junit/
│ │ │ └── jupiter/
│ │ │ └── api/
│ │ │ └── condition/
│ │ │ └── JRE.java.jte
│ │ └── testFixtures/
│ │ └── org/
│ │ └── junit/
│ │ └── jupiter/
│ │ └── api/
│ │ └── condition/
│ │ └── JavaVersionPredicates.java.jte
│ ├── test/
│ │ └── README.md
│ └── testFixtures/
│ └── java/
│ └── org/
│ └── junit/
│ └── jupiter/
│ └── api/
│ ├── AssertionTestUtils.java
│ ├── EqualsAndHashCodeAssertions.java
│ ├── TemporaryClasspathExecutor.java
│ ├── extension/
│ │ ├── DisabledInEclipse.java
│ │ ├── DisabledOnOpenJ9.java
│ │ └── ExtensionContextParameterResolver.java
│ ├── fixtures/
│ │ └── TrackLogRecords.java
│ └── io/
│ └── FailingTempDirDeletionStrategy.java
├── junit-jupiter-engine/
│ ├── junit-jupiter-engine.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── module-info.java
│ │ │ └── org/
│ │ │ └── junit/
│ │ │ └── jupiter/
│ │ │ └── engine/
│ │ │ ├── Constants.java
│ │ │ ├── JupiterTestEngine.java
│ │ │ ├── config/
│ │ │ │ ├── CachingJupiterConfiguration.java
│ │ │ │ ├── ConfigurationParameterConverter.java
│ │ │ │ ├── DefaultJupiterConfiguration.java
│ │ │ │ ├── EnumConfigurationParameterConverter.java
│ │ │ │ ├── FilteringConfigurationParameterConverter.java
│ │ │ │ ├── InstantiatingConfigurationParameterConverter.java
│ │ │ │ ├── JupiterConfiguration.java
│ │ │ │ └── package-info.java
│ │ │ ├── descriptor/
│ │ │ │ ├── AbstractExtensionContext.java
│ │ │ │ ├── CallbackSupport.java
│ │ │ │ ├── ClassBasedTestDescriptor.java
│ │ │ │ ├── ClassExtensionContext.java
│ │ │ │ ├── ClassTemplateInvocationExtensionContext.java
│ │ │ │ ├── ClassTemplateInvocationTestDescriptor.java
│ │ │ │ ├── ClassTemplateTestDescriptor.java
│ │ │ │ ├── ClassTestDescriptor.java
│ │ │ │ ├── DefaultDynamicTestInvocationContext.java
│ │ │ │ ├── DefaultTestInstanceFactoryContext.java
│ │ │ │ ├── DisplayNameUtils.java
│ │ │ │ ├── DynamicContainerTestDescriptor.java
│ │ │ │ ├── DynamicDescendantFilter.java
│ │ │ │ ├── DynamicExtensionContext.java
│ │ │ │ ├── DynamicNodeTestDescriptor.java
│ │ │ │ ├── DynamicTestTestDescriptor.java
│ │ │ │ ├── ExclusiveResourceCollector.java
│ │ │ │ ├── ExtensionUtils.java
│ │ │ │ ├── Filterable.java
│ │ │ │ ├── JupiterEngineDescriptor.java
│ │ │ │ ├── JupiterEngineExtensionContext.java
│ │ │ │ ├── JupiterTestDescriptor.java
│ │ │ │ ├── LifecycleMethodUtils.java
│ │ │ │ ├── MethodBasedTestDescriptor.java
│ │ │ │ ├── MethodExtensionContext.java
│ │ │ │ ├── MethodSourceSupport.java
│ │ │ │ ├── NestedClassTestDescriptor.java
│ │ │ │ ├── ResourceLockAware.java
│ │ │ │ ├── TemplateExecutor.java
│ │ │ │ ├── TestClassAware.java
│ │ │ │ ├── TestFactoryTestDescriptor.java
│ │ │ │ ├── TestInstanceLifecycleUtils.java
│ │ │ │ ├── TestMethodTestDescriptor.java
│ │ │ │ ├── TestTemplateExtensionContext.java
│ │ │ │ ├── TestTemplateInvocationTestDescriptor.java
│ │ │ │ ├── TestTemplateTestDescriptor.java
│ │ │ │ ├── UniqueIdPrefixTransformer.java
│ │ │ │ ├── Validatable.java
│ │ │ │ └── package-info.java
│ │ │ ├── discovery/
│ │ │ │ ├── AbstractAnnotatedDescriptorWrapper.java
│ │ │ │ ├── AbstractOrderingVisitor.java
│ │ │ │ ├── ClassOrderingVisitor.java
│ │ │ │ ├── ClassSelectorResolver.java
│ │ │ │ ├── DeclaredMethodSelector.java
│ │ │ │ ├── DefaultClassDescriptor.java
│ │ │ │ ├── DefaultClassOrdererContext.java
│ │ │ │ ├── DefaultMethodDescriptor.java
│ │ │ │ ├── DefaultMethodOrdererContext.java
│ │ │ │ ├── DiscoverySelectorResolver.java
│ │ │ │ ├── MethodOrderingVisitor.java
│ │ │ │ ├── MethodSegmentResolver.java
│ │ │ │ ├── MethodSelectorResolver.java
│ │ │ │ ├── package-info.java
│ │ │ │ └── predicates/
│ │ │ │ ├── IsTestFactoryMethod.java
│ │ │ │ ├── IsTestMethod.java
│ │ │ │ ├── IsTestTemplateMethod.java
│ │ │ │ ├── IsTestableMethod.java
│ │ │ │ ├── TestClassPredicates.java
│ │ │ │ └── package-info.java
│ │ │ ├── execution/
│ │ │ │ ├── AfterEachMethodAdapter.java
│ │ │ │ ├── BeforeEachMethodAdapter.java
│ │ │ │ ├── ConditionEvaluationException.java
│ │ │ │ ├── ConditionEvaluator.java
│ │ │ │ ├── ConstructorInvocation.java
│ │ │ │ ├── DefaultExecutableInvoker.java
│ │ │ │ ├── DefaultParameterContext.java
│ │ │ │ ├── DefaultTestInstances.java
│ │ │ │ ├── ExtensionContextSupplier.java
│ │ │ │ ├── InterceptingExecutableInvoker.java
│ │ │ │ ├── InvocationInterceptorChain.java
│ │ │ │ ├── JupiterEngineExecutionContext.java
│ │ │ │ ├── LauncherStoreFacade.java
│ │ │ │ ├── MethodInvocation.java
│ │ │ │ ├── NamespaceAwareStore.java
│ │ │ │ ├── ParameterResolutionUtils.java
│ │ │ │ ├── TestInstancesProvider.java
│ │ │ │ └── package-info.java
│ │ │ ├── extension/
│ │ │ │ ├── AutoCloseExtension.java
│ │ │ │ ├── DefaultPreInterruptContext.java
│ │ │ │ ├── DefaultRepetitionInfo.java
│ │ │ │ ├── DefaultTestReporter.java
│ │ │ │ ├── DisabledCondition.java
│ │ │ │ ├── ExtensionContextInternal.java
│ │ │ │ ├── ExtensionRegistrar.java
│ │ │ │ ├── ExtensionRegistry.java
│ │ │ │ ├── MutableExtensionRegistry.java
│ │ │ │ ├── PreInterruptCallbackInvocation.java
│ │ │ │ ├── PreInterruptCallbackInvocationFactory.java
│ │ │ │ ├── PreInterruptThreadDumpPrinter.java
│ │ │ │ ├── RepeatedTestDisplayNameFormatter.java
│ │ │ │ ├── RepeatedTestExtension.java
│ │ │ │ ├── RepeatedTestInvocationContext.java
│ │ │ │ ├── RepetitionExtension.java
│ │ │ │ ├── SameThreadTimeoutInvocation.java
│ │ │ │ ├── SeparateThreadTimeoutInvocation.java
│ │ │ │ ├── TempDirectory.java
│ │ │ │ ├── TestInfoParameterResolver.java
│ │ │ │ ├── TestReporterParameterResolver.java
│ │ │ │ ├── TimeoutConfiguration.java
│ │ │ │ ├── TimeoutDuration.java
│ │ │ │ ├── TimeoutDurationParser.java
│ │ │ │ ├── TimeoutExceptionFactory.java
│ │ │ │ ├── TimeoutExtension.java
│ │ │ │ ├── TimeoutInvocationFactory.java
│ │ │ │ └── package-info.java
│ │ │ ├── package-info.java
│ │ │ └── support/
│ │ │ ├── JupiterThrowableCollectorFactory.java
│ │ │ ├── MethodReflectionUtils.java
│ │ │ ├── OpenTest4JAndJUnit4AwareThrowableCollector.java
│ │ │ └── package-info.java
│ │ └── resources/
│ │ └── META-INF/
│ │ └── services/
│ │ └── org.junit.platform.engine.TestEngine
│ ├── test/
│ │ └── README.md
│ └── testFixtures/
│ └── java/
│ └── org/
│ └── junit/
│ └── jupiter/
│ └── engine/
│ └── discovery/
│ └── JupiterUniqueIdBuilder.java
├── junit-jupiter-migrationsupport/
│ ├── README.md
│ ├── junit-jupiter-migrationsupport.gradle.kts
│ └── src/
│ ├── main/
│ │ └── java/
│ │ ├── module-info.java
│ │ └── org/
│ │ └── junit/
│ │ └── jupiter/
│ │ └── migrationsupport/
│ │ ├── EnableJUnit4MigrationSupport.java
│ │ ├── conditions/
│ │ │ ├── IgnoreCondition.java
│ │ │ └── package-info.java
│ │ ├── package-info.java
│ │ └── rules/
│ │ ├── EnableRuleMigrationSupport.java
│ │ ├── ExpectedExceptionSupport.java
│ │ ├── ExternalResourceSupport.java
│ │ ├── TestRuleSupport.java
│ │ ├── VerifierSupport.java
│ │ ├── adapter/
│ │ │ ├── AbstractTestRuleAdapter.java
│ │ │ ├── ExpectedExceptionAdapter.java
│ │ │ ├── ExternalResourceAdapter.java
│ │ │ ├── GenericBeforeAndAfterAdvice.java
│ │ │ ├── VerifierAdapter.java
│ │ │ └── package-info.java
│ │ ├── member/
│ │ │ ├── AbstractTestRuleAnnotatedMember.java
│ │ │ ├── TestRuleAnnotatedField.java
│ │ │ ├── TestRuleAnnotatedMember.java
│ │ │ ├── TestRuleAnnotatedMethod.java
│ │ │ └── package-info.java
│ │ └── package-info.java
│ └── test/
│ └── README.md
├── junit-jupiter-params/
│ ├── junit-jupiter-params.gradle.kts
│ └── src/
│ ├── jmh/
│ │ └── java/
│ │ └── org/
│ │ └── junit/
│ │ └── jupiter/
│ │ └── params/
│ │ └── ParameterizedInvocationNameFormatterBenchmarks.java
│ ├── main/
│ │ ├── java/
│ │ │ ├── module-info.java
│ │ │ └── org/
│ │ │ └── junit/
│ │ │ └── jupiter/
│ │ │ └── params/
│ │ │ ├── AbstractParameterizedClassInvocationLifecycleMethodInvoker.java
│ │ │ ├── AfterParameterizedClassInvocation.java
│ │ │ ├── AfterParameterizedClassInvocationMethodInvoker.java
│ │ │ ├── ArgumentCountValidationMode.java
│ │ │ ├── ArgumentCountValidator.java
│ │ │ ├── ArgumentSetLifecycleMethod.java
│ │ │ ├── BeforeClassTemplateInvocationFieldInjector.java
│ │ │ ├── BeforeParameterizedClassInvocation.java
│ │ │ ├── BeforeParameterizedClassInvocationMethodInvoker.java
│ │ │ ├── ClassTemplateConstructorParameterResolver.java
│ │ │ ├── DefaultParameterInfo.java
│ │ │ ├── EvaluatedArgumentSet.java
│ │ │ ├── InstancePostProcessingClassTemplateFieldInjector.java
│ │ │ ├── Parameter.java
│ │ │ ├── ParameterInfo.java
│ │ │ ├── ParameterizedClass.java
│ │ │ ├── ParameterizedClassContext.java
│ │ │ ├── ParameterizedClassExtension.java
│ │ │ ├── ParameterizedClassInvocationContext.java
│ │ │ ├── ParameterizedDeclarationContext.java
│ │ │ ├── ParameterizedInvocationConstants.java
│ │ │ ├── ParameterizedInvocationContext.java
│ │ │ ├── ParameterizedInvocationContextProvider.java
│ │ │ ├── ParameterizedInvocationNameFormatter.java
│ │ │ ├── ParameterizedInvocationParameterResolver.java
│ │ │ ├── ParameterizedTest.java
│ │ │ ├── ParameterizedTestContext.java
│ │ │ ├── ParameterizedTestExtension.java
│ │ │ ├── ParameterizedTestInvocationContext.java
│ │ │ ├── ParameterizedTestMethodParameterResolver.java
│ │ │ ├── ParameterizedTestSpiInstantiator.java
│ │ │ ├── QuoteUtils.java
│ │ │ ├── ResolutionCache.java
│ │ │ ├── ResolverFacade.java
│ │ │ ├── aggregator/
│ │ │ │ ├── AggregateWith.java
│ │ │ │ ├── ArgumentAccessException.java
│ │ │ │ ├── ArgumentsAccessor.java
│ │ │ │ ├── ArgumentsAggregationException.java
│ │ │ │ ├── ArgumentsAggregator.java
│ │ │ │ ├── DefaultArgumentsAccessor.java
│ │ │ │ ├── SimpleArgumentsAggregator.java
│ │ │ │ └── package-info.java
│ │ │ ├── converter/
│ │ │ │ ├── AnnotationBasedArgumentConverter.java
│ │ │ │ ├── ArgumentConversionException.java
│ │ │ │ ├── ArgumentConverter.java
│ │ │ │ ├── ConvertWith.java
│ │ │ │ ├── DefaultArgumentConverter.java
│ │ │ │ ├── JavaTimeArgumentConverter.java
│ │ │ │ ├── JavaTimeConversionPattern.java
│ │ │ │ ├── SimpleArgumentConverter.java
│ │ │ │ ├── TypedArgumentConverter.java
│ │ │ │ └── package-info.java
│ │ │ ├── package-info.java
│ │ │ ├── provider/
│ │ │ │ ├── AnnotationBasedArgumentsProvider.java
│ │ │ │ ├── Arguments.java
│ │ │ │ ├── ArgumentsProvider.java
│ │ │ │ ├── ArgumentsSource.java
│ │ │ │ ├── ArgumentsSources.java
│ │ │ │ ├── ArgumentsUtils.java
│ │ │ │ ├── CsvArgumentsProvider.java
│ │ │ │ ├── CsvFileArgumentsProvider.java
│ │ │ │ ├── CsvFileSource.java
│ │ │ │ ├── CsvFileSources.java
│ │ │ │ ├── CsvParsingException.java
│ │ │ │ ├── CsvReaderFactory.java
│ │ │ │ ├── CsvSource.java
│ │ │ │ ├── CsvSources.java
│ │ │ │ ├── EmptyArgumentsProvider.java
│ │ │ │ ├── EmptySource.java
│ │ │ │ ├── EnumArgumentsProvider.java
│ │ │ │ ├── EnumSource.java
│ │ │ │ ├── EnumSources.java
│ │ │ │ ├── FieldArgumentsProvider.java
│ │ │ │ ├── FieldSource.java
│ │ │ │ ├── FieldSources.java
│ │ │ │ ├── MethodArgumentsProvider.java
│ │ │ │ ├── MethodSource.java
│ │ │ │ ├── MethodSources.java
│ │ │ │ ├── NullAndEmptySource.java
│ │ │ │ ├── NullArgumentsProvider.java
│ │ │ │ ├── NullEnum.java
│ │ │ │ ├── NullSource.java
│ │ │ │ ├── ValueArgumentsProvider.java
│ │ │ │ ├── ValueSource.java
│ │ │ │ ├── ValueSources.java
│ │ │ │ └── package-info.java
│ │ │ └── support/
│ │ │ ├── AnnotationConsumer.java
│ │ │ ├── AnnotationConsumerInitializer.java
│ │ │ ├── FieldContext.java
│ │ │ ├── ParameterDeclaration.java
│ │ │ ├── ParameterDeclarations.java
│ │ │ ├── ParameterInfo.java
│ │ │ ├── ParameterNameAndArgument.java
│ │ │ └── package-info.java
│ │ └── kotlin/
│ │ └── org/
│ │ └── junit/
│ │ └── jupiter/
│ │ └── params/
│ │ └── aggregator/
│ │ └── ArgumentsAccessor.kt
│ ├── test/
│ │ └── README.md
│ └── testFixtures/
│ └── java/
│ └── org/
│ └── junit/
│ └── jupiter/
│ └── params/
│ └── provider/
│ └── RecordArguments.java
├── junit-platform-commons/
│ ├── junit-platform-commons.gradle.kts
│ └── src/
│ ├── main/
│ │ └── java/
│ │ ├── module-info.java
│ │ └── org/
│ │ └── junit/
│ │ └── platform/
│ │ └── commons/
│ │ ├── JUnitException.java
│ │ ├── PreconditionViolationException.java
│ │ ├── annotation/
│ │ │ ├── Contract.java
│ │ │ ├── Testable.java
│ │ │ └── package-info.java
│ │ ├── function/
│ │ │ ├── Try.java
│ │ │ └── package-info.java
│ │ ├── io/
│ │ │ ├── DefaultResource.java
│ │ │ ├── Resource.java
│ │ │ ├── ResourceFilter.java
│ │ │ └── package-info.java
│ │ ├── logging/
│ │ │ ├── LogRecordListener.java
│ │ │ ├── Logger.java
│ │ │ ├── LoggerFactory.java
│ │ │ └── package-info.java
│ │ ├── package-info.java
│ │ ├── support/
│ │ │ ├── AnnotationSupport.java
│ │ │ ├── ClassSupport.java
│ │ │ ├── DefaultResource.java
│ │ │ ├── HierarchyTraversalMode.java
│ │ │ ├── ModifierSupport.java
│ │ │ ├── ReflectionSupport.java
│ │ │ ├── Resource.java
│ │ │ ├── ResourceSupport.java
│ │ │ ├── SearchOption.java
│ │ │ ├── conversion/
│ │ │ │ ├── ConversionException.java
│ │ │ │ ├── ConversionSupport.java
│ │ │ │ ├── FallbackStringToObjectConverter.java
│ │ │ │ ├── StringToBooleanConverter.java
│ │ │ │ ├── StringToCharacterConverter.java
│ │ │ │ ├── StringToClassConverter.java
│ │ │ │ ├── StringToCommonJavaTypesConverter.java
│ │ │ │ ├── StringToEnumConverter.java
│ │ │ │ ├── StringToJavaTimeConverter.java
│ │ │ │ ├── StringToNumberConverter.java
│ │ │ │ ├── StringToObjectConverter.java
│ │ │ │ └── package-info.java
│ │ │ ├── package-info.java
│ │ │ └── scanning/
│ │ │ ├── ClassFilter.java
│ │ │ ├── ClasspathScanner.java
│ │ │ └── package-info.java
│ │ └── util/
│ │ ├── AnnotationUtils.java
│ │ ├── ClassLoaderUtils.java
│ │ ├── ClassNamePatternFilterUtils.java
│ │ ├── ClassUtils.java
│ │ ├── ClasspathFileVisitor.java
│ │ ├── ClasspathScannerLoader.java
│ │ ├── CloseablePath.java
│ │ ├── CollectionUtils.java
│ │ ├── DefaultClasspathScanner.java
│ │ ├── ExceptionUtils.java
│ │ ├── FunctionUtils.java
│ │ ├── KotlinFunctionUtils.java
│ │ ├── KotlinReflectionUtils.java
│ │ ├── LruCache.java
│ │ ├── ModuleUtils.java
│ │ ├── PackageUtils.java
│ │ ├── Preconditions.java
│ │ ├── ReflectionUtils.java
│ │ ├── ResourceUtils.java
│ │ ├── RuntimeUtils.java
│ │ ├── SearchPathUtils.java
│ │ ├── ServiceLoaderUtils.java
│ │ ├── StringUtils.java
│ │ ├── ToStringBuilder.java
│ │ ├── UnrecoverableExceptions.java
│ │ └── package-info.java
│ ├── test/
│ │ └── README.md
│ └── testFixtures/
│ └── java/
│ └── org/
│ └── junit/
│ └── platform/
│ └── commons/
│ └── test/
│ ├── ConcurrencyTestingUtils.java
│ ├── IdeUtils.java
│ ├── PreconditionAssertions.java
│ ├── TestClassLoader.java
│ └── package-info.java
├── junit-platform-console/
│ ├── LICENSE-picocli.md
│ ├── junit-platform-console.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── module-info.java
│ │ │ └── org/
│ │ │ └── junit/
│ │ │ └── platform/
│ │ │ └── console/
│ │ │ ├── ConsoleLauncher.java
│ │ │ ├── ConsoleLauncherToolProvider.java
│ │ │ ├── command/
│ │ │ │ ├── BaseCommand.java
│ │ │ │ ├── CommandFacade.java
│ │ │ │ ├── CommandResult.java
│ │ │ │ ├── ConsoleTestExecutor.java
│ │ │ │ ├── CustomClassLoaderCloseStrategy.java
│ │ │ │ ├── CustomContextClassLoaderExecutor.java
│ │ │ │ ├── DiscoverTestsCommand.java
│ │ │ │ ├── DiscoveryRequestCreator.java
│ │ │ │ ├── ExecuteTestsCommand.java
│ │ │ │ ├── ExitCode.java
│ │ │ │ ├── FailFastListener.java
│ │ │ │ ├── ListTestEnginesCommand.java
│ │ │ │ ├── MainCommand.java
│ │ │ │ ├── ManifestVersionProvider.java
│ │ │ │ ├── OutputStreamConfig.java
│ │ │ │ ├── StandardStreamsHandler.java
│ │ │ │ └── package-info.java
│ │ │ ├── options/
│ │ │ │ ├── AnsiColorOptionMixin.java
│ │ │ │ ├── ClasspathEntriesConverter.java
│ │ │ │ ├── ConsoleUtils.java
│ │ │ │ ├── Details.java
│ │ │ │ ├── SelectorConverter.java
│ │ │ │ ├── TestConsoleOutputOptions.java
│ │ │ │ ├── TestConsoleOutputOptionsMixin.java
│ │ │ │ ├── TestDiscoveryOptions.java
│ │ │ │ ├── TestDiscoveryOptionsMixin.java
│ │ │ │ └── package-info.java
│ │ │ ├── output/
│ │ │ │ ├── ColorPalette.java
│ │ │ │ ├── DetailsPrintingListener.java
│ │ │ │ ├── FlatPrintingListener.java
│ │ │ │ ├── Style.java
│ │ │ │ ├── TestFeedPrintingListener.java
│ │ │ │ ├── Theme.java
│ │ │ │ ├── TreeNode.java
│ │ │ │ ├── TreePrinter.java
│ │ │ │ ├── TreePrintingListener.java
│ │ │ │ ├── VerboseTreePrintingListener.java
│ │ │ │ └── package-info.java
│ │ │ └── package-info.java
│ │ └── resources/
│ │ └── META-INF/
│ │ └── services/
│ │ └── java.util.spi.ToolProvider
│ └── test/
│ └── README.md
├── junit-platform-console-standalone/
│ └── junit-platform-console-standalone.gradle.kts
├── junit-platform-engine/
│ ├── junit-platform-engine.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── module-info.java
│ │ │ └── org/
│ │ │ └── junit/
│ │ │ └── platform/
│ │ │ └── engine/
│ │ │ ├── CancellationToken.java
│ │ │ ├── CompositeFilter.java
│ │ │ ├── CompositeTestDescriptorVisitor.java
│ │ │ ├── ConfigurationParameters.java
│ │ │ ├── DefaultDiscoveryIssue.java
│ │ │ ├── DisabledCancellationToken.java
│ │ │ ├── DiscoveryFilter.java
│ │ │ ├── DiscoveryIssue.java
│ │ │ ├── DiscoverySelector.java
│ │ │ ├── DiscoverySelectorIdentifier.java
│ │ │ ├── EngineDiscoveryListener.java
│ │ │ ├── EngineDiscoveryRequest.java
│ │ │ ├── EngineExecutionListener.java
│ │ │ ├── ExecutionRequest.java
│ │ │ ├── Filter.java
│ │ │ ├── FilterResult.java
│ │ │ ├── OutputDirectoryCreator.java
│ │ │ ├── RegularCancellationToken.java
│ │ │ ├── SelectorResolutionResult.java
│ │ │ ├── TestDescriptor.java
│ │ │ ├── TestEngine.java
│ │ │ ├── TestExecutionResult.java
│ │ │ ├── TestSource.java
│ │ │ ├── TestTag.java
│ │ │ ├── UniqueId.java
│ │ │ ├── UniqueIdFormat.java
│ │ │ ├── discovery/
│ │ │ │ ├── AbstractClassNameFilter.java
│ │ │ │ ├── ClassNameFilter.java
│ │ │ │ ├── ClassSelector.java
│ │ │ │ ├── ClasspathResourceSelector.java
│ │ │ │ ├── ClasspathRootSelector.java
│ │ │ │ ├── DirectorySelector.java
│ │ │ │ ├── DiscoverySelectorIdentifierParser.java
│ │ │ │ ├── DiscoverySelectorIdentifierParsers.java
│ │ │ │ ├── DiscoverySelectors.java
│ │ │ │ ├── ExcludeClassNameFilter.java
│ │ │ │ ├── ExcludePackageNameFilter.java
│ │ │ │ ├── FilePosition.java
│ │ │ │ ├── FileSelector.java
│ │ │ │ ├── IncludeClassNameFilter.java
│ │ │ │ ├── IncludePackageNameFilter.java
│ │ │ │ ├── IterationSelector.java
│ │ │ │ ├── MethodSelector.java
│ │ │ │ ├── ModuleSelector.java
│ │ │ │ ├── NestedClassSelector.java
│ │ │ │ ├── NestedMethodSelector.java
│ │ │ │ ├── PackageNameFilter.java
│ │ │ │ ├── PackageSelector.java
│ │ │ │ ├── UniqueIdSelector.java
│ │ │ │ ├── UriSelector.java
│ │ │ │ └── package-info.java
│ │ │ ├── package-info.java
│ │ │ ├── reporting/
│ │ │ │ ├── FileEntry.java
│ │ │ │ ├── OutputDirectoryProvider.java
│ │ │ │ ├── OutputDirectoryProviderAdapter.java
│ │ │ │ ├── ReportEntry.java
│ │ │ │ └── package-info.java
│ │ │ └── support/
│ │ │ ├── config/
│ │ │ │ ├── PrefixedConfigurationParameters.java
│ │ │ │ └── package-info.java
│ │ │ ├── descriptor/
│ │ │ │ ├── AbstractTestDescriptor.java
│ │ │ │ ├── ClassSource.java
│ │ │ │ ├── ClasspathResourceSource.java
│ │ │ │ ├── CompositeTestSource.java
│ │ │ │ ├── DefaultUriSource.java
│ │ │ │ ├── DirectorySource.java
│ │ │ │ ├── EngineDescriptor.java
│ │ │ │ ├── FilePosition.java
│ │ │ │ ├── FileSource.java
│ │ │ │ ├── FileSystemSource.java
│ │ │ │ ├── MethodSource.java
│ │ │ │ ├── PackageSource.java
│ │ │ │ ├── UriSource.java
│ │ │ │ └── package-info.java
│ │ │ ├── discovery/
│ │ │ │ ├── ClassContainerSelectorResolver.java
│ │ │ │ ├── DiscoveryIssueReporter.java
│ │ │ │ ├── EngineDiscoveryRequestResolution.java
│ │ │ │ ├── EngineDiscoveryRequestResolver.java
│ │ │ │ ├── ResourceContainerSelectorResolver.java
│ │ │ │ ├── ResourceUtils.java
│ │ │ │ ├── SelectorResolver.java
│ │ │ │ └── package-info.java
│ │ │ ├── hierarchical/
│ │ │ │ ├── BlockingAwareFuture.java
│ │ │ │ ├── CompositeLock.java
│ │ │ │ ├── DefaultParallelExecutionConfiguration.java
│ │ │ │ ├── DefaultParallelExecutionConfigurationStrategy.java
│ │ │ │ ├── DelegatingFuture.java
│ │ │ │ ├── EngineExecutionContext.java
│ │ │ │ ├── ExclusiveResource.java
│ │ │ │ ├── ForkJoinPoolHierarchicalTestExecutorService.java
│ │ │ │ ├── HierarchicalTestEngine.java
│ │ │ │ ├── HierarchicalTestExecutor.java
│ │ │ │ ├── HierarchicalTestExecutorService.java
│ │ │ │ ├── LockManager.java
│ │ │ │ ├── Node.java
│ │ │ │ ├── NodeExecutionAdvisor.java
│ │ │ │ ├── NodeTestTask.java
│ │ │ │ ├── NodeTestTaskContext.java
│ │ │ │ ├── NodeTreeWalker.java
│ │ │ │ ├── NodeUtils.java
│ │ │ │ ├── NopLock.java
│ │ │ │ ├── OpenTest4JAwareThrowableCollector.java
│ │ │ │ ├── ParallelExecutionConfiguration.java
│ │ │ │ ├── ParallelExecutionConfigurationStrategy.java
│ │ │ │ ├── ParallelHierarchicalTestExecutorServiceFactory.java
│ │ │ │ ├── ResourceLock.java
│ │ │ │ ├── SameThreadHierarchicalTestExecutorService.java
│ │ │ │ ├── SingleLock.java
│ │ │ │ ├── ThrowableCollector.java
│ │ │ │ ├── WorkerThreadPoolHierarchicalTestExecutorService.java
│ │ │ │ └── package-info.java
│ │ │ └── store/
│ │ │ ├── Namespace.java
│ │ │ ├── NamespacedHierarchicalStore.java
│ │ │ ├── NamespacedHierarchicalStoreException.java
│ │ │ └── package-info.java
│ │ └── resources/
│ │ └── META-INF/
│ │ └── services/
│ │ └── org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser
│ ├── test/
│ │ └── README.md
│ └── testFixtures/
│ └── java/
│ └── org/
│ └── junit/
│ └── platform/
│ ├── engine/
│ │ └── support/
│ │ └── hierarchical/
│ │ ├── DemoEngineExecutionContext.java
│ │ ├── DemoHierarchicalContainerDescriptor.java
│ │ ├── DemoHierarchicalEngineDescriptor.java
│ │ ├── DemoHierarchicalTestDescriptor.java
│ │ └── DemoHierarchicalTestEngine.java
│ └── fakes/
│ ├── FaultyTestEngines.java
│ ├── TestDescriptorStub.java
│ ├── TestEngineSpy.java
│ ├── TestEngineStub.java
│ └── package-info.java
├── junit-platform-launcher/
│ ├── junit-platform-launcher.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── module-info.java
│ │ │ └── org/
│ │ │ └── junit/
│ │ │ └── platform/
│ │ │ └── launcher/
│ │ │ ├── AbstractMethodFilter.java
│ │ │ ├── EngineDiscoveryResult.java
│ │ │ ├── EngineFilter.java
│ │ │ ├── ExcludeMethodFilter.java
│ │ │ ├── IncludeMethodFilter.java
│ │ │ ├── Launcher.java
│ │ │ ├── LauncherConstants.java
│ │ │ ├── LauncherDiscoveryListener.java
│ │ │ ├── LauncherDiscoveryRequest.java
│ │ │ ├── LauncherExecutionRequest.java
│ │ │ ├── LauncherInterceptor.java
│ │ │ ├── LauncherSession.java
│ │ │ ├── LauncherSessionListener.java
│ │ │ ├── MethodFilter.java
│ │ │ ├── PostDiscoveryFilter.java
│ │ │ ├── TagFilter.java
│ │ │ ├── TestExecutionListener.java
│ │ │ ├── TestIdentifier.java
│ │ │ ├── TestPlan.java
│ │ │ ├── core/
│ │ │ │ ├── ClasspathAlignmentChecker.java
│ │ │ │ ├── ClasspathAlignmentCheckingLauncherInterceptor.java
│ │ │ │ ├── CompositeEngineExecutionListener.java
│ │ │ │ ├── CompositeTestExecutionListener.java
│ │ │ │ ├── DefaultDiscoveryRequest.java
│ │ │ │ ├── DefaultLauncher.java
│ │ │ │ ├── DefaultLauncherConfig.java
│ │ │ │ ├── DefaultLauncherExecutionRequest.java
│ │ │ │ ├── DefaultLauncherSession.java
│ │ │ │ ├── DelegatingEngineExecutionListener.java
│ │ │ │ ├── DelegatingLauncher.java
│ │ │ │ ├── DelegatingLauncherDiscoveryListener.java
│ │ │ │ ├── DelegatingLauncherDiscoveryRequest.java
│ │ │ │ ├── DiscoveryIssueCollector.java
│ │ │ │ ├── DiscoveryIssueException.java
│ │ │ │ ├── DiscoveryIssueNotifier.java
│ │ │ │ ├── DiscoveryIssueReportingDiscoveryListener.java
│ │ │ │ ├── EngineDiscoveryOrchestrator.java
│ │ │ │ ├── EngineDiscoveryResultValidator.java
│ │ │ │ ├── EngineExecutionOrchestrator.java
│ │ │ │ ├── EngineFilterer.java
│ │ │ │ ├── EngineIdValidator.java
│ │ │ │ ├── ExecutionListenerAdapter.java
│ │ │ │ ├── HierarchicalOutputDirectoryCreator.java
│ │ │ │ ├── InterceptingLauncher.java
│ │ │ │ ├── InternalTestPlan.java
│ │ │ │ ├── IterationOrder.java
│ │ │ │ ├── LauncherConfig.java
│ │ │ │ ├── LauncherConfigurationParameters.java
│ │ │ │ ├── LauncherDiscoveryRequestBuilder.java
│ │ │ │ ├── LauncherDiscoveryResult.java
│ │ │ │ ├── LauncherExecutionRequestBuilder.java
│ │ │ │ ├── LauncherFactory.java
│ │ │ │ ├── LauncherListenerRegistry.java
│ │ │ │ ├── LauncherPhase.java
│ │ │ │ ├── ListenerRegistry.java
│ │ │ │ ├── MemoryCleanupListener.java
│ │ │ │ ├── OutcomeDelayingEngineExecutionListener.java
│ │ │ │ ├── ServiceLoaderRegistry.java
│ │ │ │ ├── ServiceLoaderTestEngineRegistry.java
│ │ │ │ ├── SessionPerRequestLauncher.java
│ │ │ │ ├── StackTracePruningEngineExecutionListener.java
│ │ │ │ ├── StreamInterceptingTestExecutionListener.java
│ │ │ │ ├── StreamInterceptor.java
│ │ │ │ ├── TestEngineFormatter.java
│ │ │ │ └── package-info.java
│ │ │ ├── jfr/
│ │ │ │ ├── FlightRecordingDiscoveryListener.java
│ │ │ │ ├── FlightRecordingExecutionListener.java
│ │ │ │ ├── JfrUtils.java
│ │ │ │ ├── UniqueId.java
│ │ │ │ └── package-info.java
│ │ │ ├── listeners/
│ │ │ │ ├── LoggingListener.java
│ │ │ │ ├── MutableTestExecutionSummary.java
│ │ │ │ ├── OutputDir.java
│ │ │ │ ├── SummaryGeneratingListener.java
│ │ │ │ ├── TestExecutionSummary.java
│ │ │ │ ├── UniqueIdTrackingListener.java
│ │ │ │ ├── discovery/
│ │ │ │ │ ├── AbortOnFailureLauncherDiscoveryListener.java
│ │ │ │ │ ├── CompositeLauncherDiscoveryListener.java
│ │ │ │ │ ├── LauncherDiscoveryListeners.java
│ │ │ │ │ ├── LoggingLauncherDiscoveryListener.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── package-info.java
│ │ │ │ └── session/
│ │ │ │ ├── CompositeLauncherSessionListener.java
│ │ │ │ ├── LauncherSessionListeners.java
│ │ │ │ └── package-info.java
│ │ │ ├── package-info.java
│ │ │ └── tagexpression/
│ │ │ ├── DequeStack.java
│ │ │ ├── Operator.java
│ │ │ ├── Operators.java
│ │ │ ├── ParseResult.java
│ │ │ ├── ParseResults.java
│ │ │ ├── ParseStatus.java
│ │ │ ├── Parser.java
│ │ │ ├── ShuntingYard.java
│ │ │ ├── Stack.java
│ │ │ ├── TagExpression.java
│ │ │ ├── TagExpressions.java
│ │ │ ├── Token.java
│ │ │ ├── TokenWith.java
│ │ │ ├── Tokenizer.java
│ │ │ └── package-info.java
│ │ └── resources/
│ │ └── META-INF/
│ │ └── services/
│ │ └── org.junit.platform.launcher.TestExecutionListener
│ ├── test/
│ │ └── README.md
│ └── testFixtures/
│ └── java/
│ └── org/
│ └── junit/
│ └── platform/
│ └── launcher/
│ └── core/
│ ├── ConfigurationParametersFactoryForTests.java
│ ├── LauncherFactoryForTestingPurposesOnly.java
│ ├── NamespacedHierarchicalStoreProviders.java
│ └── OutputDirectoryCreators.java
├── junit-platform-reporting/
│ ├── LICENSE-open-test-reporting.md
│ ├── junit-platform-reporting.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── module-info.java
│ │ │ └── org/
│ │ │ └── junit/
│ │ │ └── platform/
│ │ │ └── reporting/
│ │ │ ├── legacy/
│ │ │ │ ├── LegacyReportingUtils.java
│ │ │ │ ├── package-info.java
│ │ │ │ └── xml/
│ │ │ │ ├── LegacyXmlReportGeneratingListener.java
│ │ │ │ ├── XmlReportData.java
│ │ │ │ ├── XmlReportWriter.java
│ │ │ │ └── package-info.java
│ │ │ ├── open/
│ │ │ │ └── xml/
│ │ │ │ ├── GitInfoCollector.java
│ │ │ │ ├── JUnitContributor.java
│ │ │ │ ├── JUnitFactory.java
│ │ │ │ ├── LegacyReportingName.java
│ │ │ │ ├── OpenTestReportGeneratingListener.java
│ │ │ │ ├── Type.java
│ │ │ │ ├── UniqueId.java
│ │ │ │ └── package-info.java
│ │ │ └── package-info.java
│ │ └── resources/
│ │ ├── META-INF/
│ │ │ └── services/
│ │ │ ├── org.junit.platform.launcher.TestExecutionListener
│ │ │ └── org.opentest4j.reporting.tooling.spi.htmlreport.Contributor
│ │ └── org/
│ │ └── junit/
│ │ └── platform/
│ │ └── reporting/
│ │ └── open/
│ │ └── xml/
│ │ ├── catalog.xml
│ │ └── junit.xsd
│ ├── test/
│ │ └── README.md
│ └── testFixtures/
│ └── java/
│ └── org/
│ └── junit/
│ └── platform/
│ └── reporting/
│ └── testutil/
│ └── FileUtils.java
├── junit-platform-suite/
│ ├── junit-platform-suite.gradle.kts
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── module-info.java
│ └── test/
│ └── README.md
├── junit-platform-suite-api/
│ ├── junit-platform-suite-api.gradle.kts
│ └── src/
│ ├── main/
│ │ └── java/
│ │ ├── module-info.java
│ │ └── org/
│ │ └── junit/
│ │ └── platform/
│ │ └── suite/
│ │ └── api/
│ │ ├── AfterSuite.java
│ │ ├── BeforeSuite.java
│ │ ├── ConfigurationParameter.java
│ │ ├── ConfigurationParameters.java
│ │ ├── ConfigurationParametersResource.java
│ │ ├── ConfigurationParametersResources.java
│ │ ├── DisableParentConfigurationParameters.java
│ │ ├── ExcludeClassNamePatterns.java
│ │ ├── ExcludeEngines.java
│ │ ├── ExcludePackages.java
│ │ ├── ExcludeTags.java
│ │ ├── IncludeClassNamePatterns.java
│ │ ├── IncludeEngines.java
│ │ ├── IncludePackages.java
│ │ ├── IncludeTags.java
│ │ ├── Select.java
│ │ ├── SelectClasses.java
│ │ ├── SelectClasspathResource.java
│ │ ├── SelectClasspathResources.java
│ │ ├── SelectDirectories.java
│ │ ├── SelectFile.java
│ │ ├── SelectFiles.java
│ │ ├── SelectMethod.java
│ │ ├── SelectMethods.java
│ │ ├── SelectModules.java
│ │ ├── SelectPackages.java
│ │ ├── SelectUris.java
│ │ ├── Selects.java
│ │ ├── Suite.java
│ │ ├── SuiteDisplayName.java
│ │ └── package-info.java
│ └── test/
│ └── README.md
├── junit-platform-suite-engine/
│ ├── junit-platform-suite-engine.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── module-info.java
│ │ │ └── org/
│ │ │ └── junit/
│ │ │ └── platform/
│ │ │ └── suite/
│ │ │ └── engine/
│ │ │ ├── AdditionalDiscoverySelectors.java
│ │ │ ├── ClassSelectorResolver.java
│ │ │ ├── DiscoverySelectorResolver.java
│ │ │ ├── IsSuiteClass.java
│ │ │ ├── LifecycleMethodUtils.java
│ │ │ ├── NoTestsDiscoveredException.java
│ │ │ ├── SuiteEngineDescriptor.java
│ │ │ ├── SuiteLauncher.java
│ │ │ ├── SuiteLauncherDiscoveryRequestBuilder.java
│ │ │ ├── SuiteTestDescriptor.java
│ │ │ ├── SuiteTestEngine.java
│ │ │ └── package-info.java
│ │ └── resources/
│ │ └── META-INF/
│ │ └── services/
│ │ └── org.junit.platform.engine.TestEngine
│ └── test/
│ └── README.md
├── junit-platform-testkit/
│ ├── LICENSE.md
│ ├── junit-platform-testkit.gradle.kts
│ └── src/
│ ├── main/
│ │ └── java/
│ │ ├── module-info.java
│ │ └── org/
│ │ └── junit/
│ │ └── platform/
│ │ └── testkit/
│ │ ├── engine/
│ │ │ ├── Assertions.java
│ │ │ ├── EngineDiscoveryResults.java
│ │ │ ├── EngineExecutionResults.java
│ │ │ ├── EngineTestKit.java
│ │ │ ├── Event.java
│ │ │ ├── EventConditions.java
│ │ │ ├── EventStatistics.java
│ │ │ ├── EventType.java
│ │ │ ├── Events.java
│ │ │ ├── Execution.java
│ │ │ ├── ExecutionRecorder.java
│ │ │ ├── Executions.java
│ │ │ ├── TerminationInfo.java
│ │ │ ├── TestExecutionResultConditions.java
│ │ │ └── package-info.java
│ │ └── package-info.java
│ └── test/
│ └── README.md
├── junit-start/
│ ├── junit-start.gradle.kts
│ └── src/
│ └── main/
│ └── java/
│ ├── module-info.java
│ └── org/
│ └── junit/
│ └── start/
│ ├── JUnit.java
│ └── package-info.java
├── junit-vintage-engine/
│ ├── junit-vintage-engine.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── module-info.java
│ │ │ └── org/
│ │ │ └── junit/
│ │ │ └── vintage/
│ │ │ └── engine/
│ │ │ ├── Constants.java
│ │ │ ├── JUnit4VersionCheck.java
│ │ │ ├── VintageTestEngine.java
│ │ │ ├── descriptor/
│ │ │ │ ├── DescriptionUtils.java
│ │ │ │ ├── OrFilter.java
│ │ │ │ ├── RunnerDecorator.java
│ │ │ │ ├── RunnerRequest.java
│ │ │ │ ├── RunnerTestDescriptor.java
│ │ │ │ ├── TestSourceProvider.java
│ │ │ │ ├── VintageEngineDescriptor.java
│ │ │ │ ├── VintageTestDescriptor.java
│ │ │ │ └── package-info.java
│ │ │ ├── discovery/
│ │ │ │ ├── ClassSelectorResolver.java
│ │ │ │ ├── DefensiveAllDefaultPossibilitiesBuilder.java
│ │ │ │ ├── FilterableIgnoringRunnerDecorator.java
│ │ │ │ ├── IgnoringRunnerDecorator.java
│ │ │ │ ├── IsPotentialJUnit4TestClass.java
│ │ │ │ ├── IsPotentialJUnit4TestMethod.java
│ │ │ │ ├── MethodSelectorResolver.java
│ │ │ │ ├── RunnerTestDescriptorPostProcessor.java
│ │ │ │ ├── UniqueIdFilter.java
│ │ │ │ ├── VintageDiscoverer.java
│ │ │ │ └── package-info.java
│ │ │ ├── execution/
│ │ │ │ ├── CancellationTokenAwareRunNotifier.java
│ │ │ │ ├── EventType.java
│ │ │ │ ├── RunListenerAdapter.java
│ │ │ │ ├── RunnerExecutor.java
│ │ │ │ ├── TestRun.java
│ │ │ │ ├── VintageExecutor.java
│ │ │ │ └── package-info.java
│ │ │ ├── package-info.java
│ │ │ └── support/
│ │ │ ├── UniqueIdReader.java
│ │ │ ├── UniqueIdStringifier.java
│ │ │ └── package-info.java
│ │ └── resources/
│ │ └── META-INF/
│ │ └── services/
│ │ └── org.junit.platform.engine.TestEngine
│ ├── test/
│ │ ├── java/
│ │ │ └── org/
│ │ │ └── junit/
│ │ │ └── vintage/
│ │ │ └── engine/
│ │ │ ├── JUnit4ParameterizedTests.java
│ │ │ ├── JUnit4VersionCheckTests.java
│ │ │ ├── VintageLauncherIntegrationTests.java
│ │ │ ├── VintageTestEngineBasicTests.java
│ │ │ ├── VintageTestEngineDiscoveryTests.java
│ │ │ ├── VintageTestEngineExecutionTests.java
│ │ │ ├── VintageTestEngineTestSuite.java
│ │ │ ├── VintageUniqueIdBuilder.java
│ │ │ ├── descriptor/
│ │ │ │ ├── DescriptionUtilsTests.java
│ │ │ │ ├── OrFilterTests.java
│ │ │ │ ├── TestSourceProviderTests.java
│ │ │ │ └── VintageTestDescriptorTests.java
│ │ │ ├── discovery/
│ │ │ │ ├── IsPotentialJUnit4TestClassTests.java
│ │ │ │ ├── RunnerTestDescriptorPostProcessorTests.java
│ │ │ │ └── VintageDiscovererTests.java
│ │ │ ├── execution/
│ │ │ │ ├── ParallelExecutionIntegrationTests.java
│ │ │ │ └── TestRunTests.java
│ │ │ └── support/
│ │ │ ├── UniqueIdReaderTests.java
│ │ │ └── UniqueIdStringifierTests.java
│ │ └── resources/
│ │ ├── junit-platform.properties
│ │ ├── log4j2-test.xml
│ │ └── vintage-testjar.jar
│ └── testFixtures/
│ ├── groovy/
│ │ └── org/
│ │ └── junit/
│ │ └── vintage/
│ │ └── engine/
│ │ └── samples/
│ │ └── spock/
│ │ └── SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy
│ └── java/
│ └── org/
│ └── junit/
│ ├── platform/
│ │ └── runner/
│ │ └── JUnitPlatform.java
│ └── vintage/
│ └── engine/
│ └── samples/
│ ├── PlainOldJavaClassWithoutAnyTestsTestCase.java
│ ├── junit3/
│ │ ├── AbstractJUnit3TestCase.java
│ │ ├── IgnoredJUnit3TestCase.java
│ │ ├── JUnit3ParallelSuiteWithSubsuites.java
│ │ ├── JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java
│ │ ├── JUnit3SuiteWithSubsuites.java
│ │ ├── JUnit4SuiteWithIgnoredJUnit3TestCase.java
│ │ └── PlainJUnit3TestCaseWithSingleTestWhichFails.java
│ └── junit4/
│ ├── AbstractJUnit4TestCase.java
│ ├── AbstractJunit4TestCaseWithConstructorParameter.java
│ ├── CancellingTestCase.java
│ ├── Categories.java
│ ├── CompletelyDynamicTestCase.java
│ ├── ConcreteJUnit4TestCase.java
│ ├── ConfigurableRunner.java
│ ├── DynamicRunner.java
│ ├── EmptyIgnoredTestCase.java
│ ├── EnclosedJUnit4TestCase.java
│ ├── EnclosedWithParameterizedChildrenJUnit4TestCase.java
│ ├── ExceptionThrowingRunner.java
│ ├── IgnoredJUnit4TestCase.java
│ ├── IgnoredJUnit4TestCaseWithNotFilterableRunner.java
│ ├── IgnoredParameterizedTestCase.java
│ ├── JUnit4ParallelClassesTestCase.java
│ ├── JUnit4ParallelMethodsTestCase.java
│ ├── JUnit4ParameterizedTestCase.java
│ ├── JUnit4SuiteOfSuiteWithFilterableChildRunner.java
│ ├── JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java
│ ├── JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java
│ ├── JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java
│ ├── JUnit4SuiteWithExceptionThrowingRunner.java
│ ├── JUnit4SuiteWithIgnoredJUnit4TestCase.java
│ ├── JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java
│ ├── JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java
│ ├── JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java
│ ├── JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java
│ ├── JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java
│ ├── JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java
│ ├── JUnit4SuiteWithTwoTestCases.java
│ ├── JUnit4TestCaseWithAssumptionFailureInBeforeClass.java
│ ├── JUnit4TestCaseWithDistinguishableOverloadedMethod.java
│ ├── JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java
│ ├── JUnit4TestCaseWithErrorInAfterClass.java
│ ├── JUnit4TestCaseWithErrorInBeforeClass.java
│ ├── JUnit4TestCaseWithExceptionThrowingRunner.java
│ ├── JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java
│ ├── JUnit4TestCaseWithIndistinguishableOverloadedMethod.java
│ ├── JUnit4TestCaseWithNotFilterableRunner.java
│ ├── JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java
│ ├── JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java
│ ├── Label.java
│ ├── MalformedJUnit4TestCase.java
│ ├── NotFilterableRunner.java
│ ├── ParameterizedTestCase.java
│ ├── ParameterizedTimingTestCase.java
│ ├── ParameterizedWithAfterParamFailureTestCase.java
│ ├── ParameterizedWithBeforeParamFailureTestCase.java
│ ├── PlainJUnit4TestCaseWithFiveTestMethods.java
│ ├── PlainJUnit4TestCaseWithLifecycleMethods.java
│ ├── PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java
│ ├── PlainJUnit4TestCaseWithSingleTestWhichFails.java
│ ├── PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java
│ ├── PlainJUnit4TestCaseWithTwoTestMethods.java
│ ├── RunnerThatOnlyReportsFailures.java
│ ├── RunnerWithCustomUniqueIdsAndDisplayNames.java
│ ├── SingleFailingTheoryTestCase.java
│ ├── TestCaseRunWithJUnitPlatformRunner.java
│ └── package-info.java
├── jupiter-tests/
│ ├── jupiter-tests.gradle.kts
│ └── src/
│ ├── templates/
│ │ └── resources/
│ │ └── test/
│ │ └── org/
│ │ └── junit/
│ │ └── jupiter/
│ │ └── api/
│ │ └── condition/
│ │ ├── DisabledOnJreConditionTests.java.jte
│ │ ├── DisabledOnJreIntegrationTests.java.jte
│ │ ├── EnabledOnJreConditionTests.java.jte
│ │ └── EnabledOnJreIntegrationTests.java.jte
│ └── test/
│ ├── groovy/
│ │ └── org/
│ │ └── junit/
│ │ └── jupiter/
│ │ └── api/
│ │ ├── GroovyAssertEqualsTests.groovy
│ │ ├── GroovyAssertNotEqualsTests.groovy
│ │ └── PrimitiveAndWrapperTypeHelpers.groovy
│ ├── java/
│ │ ├── DefaultPackageTestCase.java
│ │ ├── example/
│ │ │ └── B_TestCase.java
│ │ └── org/
│ │ └── junit/
│ │ └── jupiter/
│ │ ├── JupiterTestSuite.java
│ │ ├── api/
│ │ │ ├── AssertAllAssertionsTests.java
│ │ │ ├── AssertArrayEqualsAssertionsTests.java
│ │ │ ├── AssertDoesNotThrowAssertionsTests.java
│ │ │ ├── AssertEqualsAssertionsTests.java
│ │ │ ├── AssertFalseAssertionsTests.java
│ │ │ ├── AssertInstanceOfAssertionsTests.java
│ │ │ ├── AssertIterableEqualsAssertionsTests.java
│ │ │ ├── AssertLinesMatchAssertionsTests.java
│ │ │ ├── AssertNotEqualsAssertionsTests.java
│ │ │ ├── AssertNotNullAssertionsTests.java
│ │ │ ├── AssertNotSameAssertionsTests.java
│ │ │ ├── AssertNullAssertionsTests.java
│ │ │ ├── AssertSameAssertionsTests.java
│ │ │ ├── AssertThrowsAssertionsTests.java
│ │ │ ├── AssertThrowsExactlyAssertionsTests.java
│ │ │ ├── AssertTimeoutAssertionsTests.java
│ │ │ ├── AssertTimeoutPreemptivelyAssertionsTests.java
│ │ │ ├── AssertTrueAssertionsTests.java
│ │ │ ├── AssertionFailureBuilderTest.java
│ │ │ ├── AssumptionsTests.java
│ │ │ ├── ConstantTests.java
│ │ │ ├── DisplayNameGenerationInheritanceTestCase.java
│ │ │ ├── DisplayNameGenerationTests.java
│ │ │ ├── DynamicContainerTests.java
│ │ │ ├── DynamicTestTests.java
│ │ │ ├── EnigmaThrowable.java
│ │ │ ├── FailAssertionsTests.java
│ │ │ ├── IndicativeSentencesGenerationInheritanceTestCase.java
│ │ │ ├── IndicativeSentencesNestedTestCase.java
│ │ │ ├── IndicativeSentencesOnSubClassScenarioOneTestCase.java
│ │ │ ├── IndicativeSentencesOnSubClassTestCase.java
│ │ │ ├── IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java
│ │ │ ├── IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java
│ │ │ ├── IndicativeSentencesRuntimeEnclosingTypeTestCase.java
│ │ │ ├── IndicativeSentencesTopLevelTestCase.java
│ │ │ ├── IterableFactory.java
│ │ │ ├── MediaTypeTests.java
│ │ │ ├── RandomlyOrderedTests.java
│ │ │ ├── condition/
│ │ │ │ ├── AbstractExecutionConditionTests.java
│ │ │ │ ├── ConditionEvaluationResultTests.java
│ │ │ │ ├── DisabledForJreRangeConditionTests.java
│ │ │ │ ├── DisabledForJreRangeIntegrationTests.java
│ │ │ │ ├── DisabledIfConditionClassLoaderTests.java
│ │ │ │ ├── DisabledIfConditionTests.java
│ │ │ │ ├── DisabledIfEnvironmentVariableConditionTests.java
│ │ │ │ ├── DisabledIfEnvironmentVariableIntegrationTests.java
│ │ │ │ ├── DisabledIfIntegrationTests.java
│ │ │ │ ├── DisabledIfSystemPropertyConditionTests.java
│ │ │ │ ├── DisabledIfSystemPropertyIntegrationTests.java
│ │ │ │ ├── DisabledOnOsConditionTests.java
│ │ │ │ ├── DisabledOnOsIntegrationTests.java
│ │ │ │ ├── EnabledForJreRangeConditionTests.java
│ │ │ │ ├── EnabledForJreRangeIntegrationTests.java
│ │ │ │ ├── EnabledIfConditionClassLoaderTests.java
│ │ │ │ ├── EnabledIfConditionTests.java
│ │ │ │ ├── EnabledIfEnvironmentVariableConditionTests.java
│ │ │ │ ├── EnabledIfEnvironmentVariableIntegrationTests.java
│ │ │ │ ├── EnabledIfIntegrationTests.java
│ │ │ │ ├── EnabledIfSystemPropertyConditionTests.java
│ │ │ │ ├── EnabledIfSystemPropertyIntegrationTests.java
│ │ │ │ ├── EnabledOnOsConditionTests.java
│ │ │ │ ├── EnabledOnOsIntegrationTests.java
│ │ │ │ ├── JRETests.java
│ │ │ │ ├── OSTests.java
│ │ │ │ ├── StaticConditionMethods.java
│ │ │ │ └── TestDoubleJRETests.java
│ │ │ ├── extension/
│ │ │ │ ├── CloseableResourceIntegrationTests.java
│ │ │ │ ├── DeprecatedMediaTypeTests.java
│ │ │ │ ├── ExecutableInvokerIntegrationTests.java
│ │ │ │ ├── ExtensionComposabilityTests.java
│ │ │ │ ├── Heavyweight.java
│ │ │ │ ├── HeavyweightTests.java
│ │ │ │ ├── KitchenSinkExtension.java
│ │ │ │ ├── MediaTypeInteroperabilityTests.java
│ │ │ │ └── support/
│ │ │ │ └── TypeBasedParameterResolverTests.java
│ │ │ ├── io/
│ │ │ │ └── TempDirDeletionStrategyTests.java
│ │ │ ├── parallel/
│ │ │ │ ├── LockTests.java
│ │ │ │ ├── ResourceLockAnnotationTests.java
│ │ │ │ └── ResourceLocksProviderTests.java
│ │ │ ├── subpackage/
│ │ │ │ ├── SubclassedAssertionsTests.java
│ │ │ │ └── SubclassedAssumptionsTests.java
│ │ │ ├── timeout/
│ │ │ │ └── PreemptiveTimeoutUtilsTest.java
│ │ │ └── util/
│ │ │ ├── DefaultLocaleTests.java
│ │ │ ├── DefaultTimeZoneTests.java
│ │ │ ├── JupiterPropertyUtilsTests.java
│ │ │ └── SystemPropertiesExtensionTests.java
│ │ ├── engine/
│ │ │ ├── AbstractJupiterTestEngineTests.java
│ │ │ ├── AtypicalJvmMethodNameTests.java
│ │ │ ├── BeforeAllAndAfterAllComposedAnnotationTests.java
│ │ │ ├── BeforeEachAndAfterEachComposedAnnotationTests.java
│ │ │ ├── ClassTemplateInvocationTests.java
│ │ │ ├── DefaultExecutionModeTests.java
│ │ │ ├── DefaultMethodTests.java
│ │ │ ├── DisabledTests.java
│ │ │ ├── DynamicNodeGenerationTests.java
│ │ │ ├── ExceptionHandlingTests.java
│ │ │ ├── ExecutionCancellationTests.java
│ │ │ ├── FailedAssumptionsTests.java
│ │ │ ├── InvalidLifecycleMethodConfigurationTests.java
│ │ │ ├── JupiterTestEngineTests.java
│ │ │ ├── LifecycleMethodOverridingTests.java
│ │ │ ├── MultipleTestableAnnotationsTests.java
│ │ │ ├── NestedTestClassesTests.java
│ │ │ ├── NestedWithInheritanceTests.java
│ │ │ ├── NestedWithSeparateInheritanceTests.java
│ │ │ ├── OverloadedTestMethodTests.java
│ │ │ ├── RecordTests.java
│ │ │ ├── ReportingTests.java
│ │ │ ├── SealedClassTests.java
│ │ │ ├── StandardTestClassTests.java
│ │ │ ├── StaticNestedBeforeAllAndAfterAllMethodsTests.java
│ │ │ ├── StaticNestedTestCase.java
│ │ │ ├── TestClassInheritanceTests.java
│ │ │ ├── TestInstanceLifecycleConfigurationTests.java
│ │ │ ├── TestInstanceLifecycleKotlinTests.java
│ │ │ ├── TestInstanceLifecycleTests.java
│ │ │ ├── TestMethodOverridingTests.java
│ │ │ ├── TestTemplateInvocationTests.java
│ │ │ ├── TopLevelComposedNested.java
│ │ │ ├── TopLevelNestedTestCase.java
│ │ │ ├── bridge/
│ │ │ │ ├── AbstractNonGenericTests.java
│ │ │ │ ├── AbstractNumberTests.java
│ │ │ │ ├── BridgeMethodTests.java
│ │ │ │ ├── ChildWithBridgeMethods.java
│ │ │ │ ├── ChildWithoutBridgeMethods.java
│ │ │ │ ├── NumberResolver.java
│ │ │ │ ├── NumberTestGroup.java
│ │ │ │ └── PackagePrivateParent.java
│ │ │ ├── config/
│ │ │ │ ├── CachingJupiterConfigurationTests.java
│ │ │ │ ├── DefaultJupiterConfigurationTests.java
│ │ │ │ └── InstantiatingConfigurationParameterConverterTests.java
│ │ │ ├── descriptor/
│ │ │ │ ├── CustomDisplayNameGenerator.java
│ │ │ │ ├── DisplayNameUtilsTests.java
│ │ │ │ ├── ExtensionContextTests.java
│ │ │ │ ├── ExtensionsUtilsTests.java
│ │ │ │ ├── JupiterTestDescriptorTests.java
│ │ │ │ ├── LauncherStoreFacadeTest.java
│ │ │ │ ├── LifecycleMethodUtilsTests.java
│ │ │ │ ├── NamespaceTests.java
│ │ │ │ ├── ResourceAutoClosingTests.java
│ │ │ │ ├── TestFactoryTestDescriptorTests.java
│ │ │ │ ├── TestInstanceLifecycleUtilsTests.java
│ │ │ │ ├── TestTemplateInvocationTestDescriptorTests.java
│ │ │ │ ├── TestTemplateTestDescriptorTests.java
│ │ │ │ └── subpackage/
│ │ │ │ ├── Class1WithTestCases.java
│ │ │ │ ├── Class2WithTestCases.java
│ │ │ │ ├── ClassWithStaticInnerTestCases.java
│ │ │ │ └── ClassWithoutTestCases.java
│ │ │ ├── discovery/
│ │ │ │ ├── DiscoverySelectorResolverTests.java
│ │ │ │ ├── DiscoveryTests.java
│ │ │ │ └── predicates/
│ │ │ │ ├── IsTestFactoryMethodTests.java
│ │ │ │ ├── IsTestMethodTests.java
│ │ │ │ ├── IsTestTemplateMethodTests.java
│ │ │ │ └── TestClassPredicatesTests.java
│ │ │ ├── execution/
│ │ │ │ ├── AbstractExecutableInvokerTests.java
│ │ │ │ ├── DefaultExecutableInvokerTests.java
│ │ │ │ ├── DefaultTestInstancesTests.java
│ │ │ │ ├── DynamicTestIntegrationTests.java
│ │ │ │ ├── ExtensionContextStoreConcurrencyTests.java
│ │ │ │ ├── ExtensionContextStoreTests.java
│ │ │ │ ├── InterceptingExecutableInvokerTests.java
│ │ │ │ ├── JupiterEngineExecutionContextTests.java
│ │ │ │ ├── ParameterResolutionUtilsTests.java
│ │ │ │ ├── UniqueIdParsingForArrayParameterIntegrationTests.java
│ │ │ │ └── injection/
│ │ │ │ └── sample/
│ │ │ │ ├── CustomAnnotation.java
│ │ │ │ ├── CustomAnnotationParameterResolver.java
│ │ │ │ ├── CustomType.java
│ │ │ │ ├── CustomTypeParameterResolver.java
│ │ │ │ ├── DoubleParameterResolver.java
│ │ │ │ ├── LongParameterResolver.java
│ │ │ │ ├── MapOfListsTypeBasedParameterResolver.java
│ │ │ │ ├── MapOfStringsParameterResolver.java
│ │ │ │ ├── NullIntegerParameterResolver.java
│ │ │ │ ├── NumberParameterResolver.java
│ │ │ │ ├── PrimitiveArrayParameterResolver.java
│ │ │ │ └── PrimitiveIntegerParameterResolver.java
│ │ │ ├── extension/
│ │ │ │ ├── AutoCloseTests.java
│ │ │ │ ├── BeforeAndAfterAllTests.java
│ │ │ │ ├── BeforeAndAfterEachTests.java
│ │ │ │ ├── BeforeAndAfterTestExecutionCallbackTests.java
│ │ │ │ ├── CloseablePathTests.java
│ │ │ │ ├── ConfigLoaderExtension.java
│ │ │ │ ├── DefaultTestReporterTests.java
│ │ │ │ ├── EnigmaException.java
│ │ │ │ ├── EventuallyInterruptibleInvocation.java
│ │ │ │ ├── ExecutionConditionTests.java
│ │ │ │ ├── ExtensionContextExecutionTests.java
│ │ │ │ ├── ExtensionRegistrationViaParametersAndFieldsTests.java
│ │ │ │ ├── ExtensionRegistryTests.java
│ │ │ │ ├── InvocationInterceptorTests.java
│ │ │ │ ├── LifecycleMethodExecutionExceptionHandlerTests.java
│ │ │ │ ├── OrderedClassTests.java
│ │ │ │ ├── OrderedMethodTests.java
│ │ │ │ ├── OrderedProgrammaticExtensionRegistrationTests.java
│ │ │ │ ├── ParameterResolverTests.java
│ │ │ │ ├── PreInterruptCallbackTests.java
│ │ │ │ ├── ProgrammaticExtensionRegistrationTests.java
│ │ │ │ ├── RepeatedTestTests.java
│ │ │ │ ├── SameThreadTimeoutInvocationTests.java
│ │ │ │ ├── SeparateThreadTimeoutInvocationTests.java
│ │ │ │ ├── ServiceLoaderExtension.java
│ │ │ │ ├── TempDirectoryCleanupTests.java
│ │ │ │ ├── TempDirectoryMetaAnnotationTests.java
│ │ │ │ ├── TempDirectoryPreconditionTests.java
│ │ │ │ ├── TempDirectoryTests.java
│ │ │ │ ├── TestExecutionExceptionHandlerTests.java
│ │ │ │ ├── TestInfoParameterResolverTests.java
│ │ │ │ ├── TestInstanceFactoryTests.java
│ │ │ │ ├── TestInstancePostProcessorAndPreDestroyCallbackTests.java
│ │ │ │ ├── TestInstancePostProcessorTests.java
│ │ │ │ ├── TestInstancePreConstructCallbackTests.java
│ │ │ │ ├── TestInstancePreDestroyCallbackTests.java
│ │ │ │ ├── TestInstancePreDestroyCallbackUtilityMethodTests.java
│ │ │ │ ├── TestReporterParameterResolverTests.java
│ │ │ │ ├── TestWatcherTests.java
│ │ │ │ ├── TimeoutConfigurationTests.java
│ │ │ │ ├── TimeoutDurationParserTests.java
│ │ │ │ ├── TimeoutDurationTests.java
│ │ │ │ ├── TimeoutExceptionFactoryTests.java
│ │ │ │ ├── TimeoutExtensionTests.java
│ │ │ │ ├── TimeoutInvocationFactoryTests.java
│ │ │ │ └── sub/
│ │ │ │ ├── AlwaysDisabledCondition.java
│ │ │ │ ├── AnotherAlwaysDisabledCondition.java
│ │ │ │ └── SystemPropertyCondition.java
│ │ │ ├── subpackage/
│ │ │ │ └── SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java
│ │ │ └── support/
│ │ │ └── OpenTest4JAndJUnit4AwareThrowableCollectorTests.java
│ │ ├── migrationsupport/
│ │ │ ├── JupiterMigrationSupportTestSuite.java
│ │ │ ├── conditions/
│ │ │ │ ├── IgnoreAnnotationIntegrationTests.java
│ │ │ │ └── IgnoreConditionTests.java
│ │ │ └── rules/
│ │ │ ├── AbstractTestRuleAdapterTests.java
│ │ │ ├── EnableRuleMigrationSupportWithBothRuleTypesTests.java
│ │ │ ├── ExpectedExceptionSupportTests.java
│ │ │ ├── ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java
│ │ │ ├── ExternalResourceSupportForMixedMethodAndFieldRulesTests.java
│ │ │ ├── ExternalResourceSupportForMultipleFieldRulesTests.java
│ │ │ ├── ExternalResourceSupportForMultipleMethodRulesTests.java
│ │ │ ├── ExternalResourceSupportForTemporaryFolderFieldTests.java
│ │ │ ├── ExternalResourceSupportWithInheritanceTests.java
│ │ │ ├── ExternalResourceWithoutAdapterTests.java
│ │ │ ├── FailAfterAllHelper.java
│ │ │ ├── LauncherBasedEnableRuleMigrationSupportTests.java
│ │ │ ├── VerifierSupportForMixedMethodAndFieldRulesTests.java
│ │ │ ├── WrongExtendWithForVerifierFieldTests.java
│ │ │ └── WrongExtendWithForVerifierMethodTests.java
│ │ └── params/
│ │ ├── ParameterInfoIntegrationTests.java
│ │ ├── ParameterizedClassIntegrationTests.java
│ │ ├── ParameterizedInvocationNameFormatterTests.java
│ │ ├── ParameterizedTestContextTests.java
│ │ ├── ParameterizedTestExtensionTests.java
│ │ ├── ParameterizedTestIntegrationTests.java
│ │ ├── ParameterizedTestSuite.java
│ │ ├── aggregator/
│ │ │ ├── AggregatorIntegrationTests.java
│ │ │ └── DefaultArgumentsAccessorTests.java
│ │ ├── converter/
│ │ │ ├── DefaultArgumentConverterTests.java
│ │ │ ├── JavaTimeArgumentConverterTests.java
│ │ │ └── TypedArgumentConverterTests.java
│ │ ├── provider/
│ │ │ ├── AnnotationBasedArgumentsProviderTests.java
│ │ │ ├── ArgumentsTests.java
│ │ │ ├── CsvArgumentsProviderTests.java
│ │ │ ├── CsvFileArgumentsProviderTests.java
│ │ │ ├── EnumArgumentsProviderTests.java
│ │ │ ├── EnumSourceTests.java
│ │ │ ├── FieldArgumentsProviderTests.java
│ │ │ ├── MethodArgumentsProviderTests.java
│ │ │ ├── MockCsvAnnotationBuilder.java
│ │ │ └── ValueArgumentsProviderTests.java
│ │ └── support/
│ │ ├── AnnotationConsumerInitializerTests.java
│ │ └── DeprecatedParameterInfoIntegrationTests.java
│ ├── kotlin/
│ │ └── org/
│ │ └── junit/
│ │ └── jupiter/
│ │ ├── api/
│ │ │ └── kotlin/
│ │ │ ├── GenericInlineValueClassTests.kt
│ │ │ ├── KotlinAssertTimeoutAssertionsTests.kt
│ │ │ ├── KotlinAssertionsTests.kt
│ │ │ ├── KotlinDynamicTests.kt
│ │ │ ├── KotlinFailAssertionsTests.kt
│ │ │ ├── KotlinSuspendFunctionsTests.kt
│ │ │ └── PrimitiveWrapperInlineValueClassTests.kt
│ │ ├── engine/
│ │ │ └── kotlin/
│ │ │ ├── ArbitraryNamingKotlinTestCase.kt
│ │ │ ├── InstancePerClassKotlinTestCase.kt
│ │ │ ├── InstancePerMethodKotlinTestCase.kt
│ │ │ ├── KotlinDefaultImplsTestCase.kt
│ │ │ ├── KotlinInterfaceImplementationTestCase.kt
│ │ │ └── KotlinInterfaceTestCase.kt
│ │ └── params/
│ │ ├── ParameterizedClassKotlinIntegrationTests.kt
│ │ ├── ParameterizedInvocationNameFormatterIntegrationTests.kt
│ │ ├── ParameterizedTestKotlinSequenceIntegrationTests.kt
│ │ ├── aggregator/
│ │ │ ├── ArgumentsAccessorKotlinTests.kt
│ │ │ └── DisplayNameTests.kt
│ │ └── converter/
│ │ └── TypedArgumentConverterKotlinTests.kt
│ └── resources/
│ ├── META-INF/
│ │ └── services/
│ │ └── org.junit.jupiter.api.extension.Extension
│ ├── junit-platform.properties
│ ├── jupiter-testjar.jar
│ ├── log4j2-test.xml
│ └── org/
│ └── junit/
│ └── jupiter/
│ └── params/
│ ├── provider/
│ │ ├── broken.csv
│ │ ├── default-max-chars.csv
│ │ ├── exceeds-default-max-chars.csv
│ │ ├── leading-trailing-spaces.csv
│ │ └── single-column.csv
│ ├── two-column-with-headers.csv
│ └── two-column.csv
├── platform-tests/
│ ├── platform-tests.gradle.kts
│ └── src/
│ ├── jmh/
│ │ └── java/
│ │ └── org/
│ │ └── junit/
│ │ └── jupiter/
│ │ └── jmh/
│ │ └── AssertionBenchmarks.java
│ ├── processStarter/
│ │ └── java/
│ │ └── org/
│ │ └── junit/
│ │ └── platform/
│ │ └── tests/
│ │ └── process/
│ │ ├── OutputFiles.java
│ │ ├── ProcessResult.java
│ │ ├── ProcessStarter.java
│ │ ├── WatchedOutput.java
│ │ ├── WatchedProcess.java
│ │ └── package-info.java
│ └── test/
│ ├── java/
│ │ ├── DefaultPackageTestCase.java
│ │ └── org/
│ │ └── junit/
│ │ └── platform/
│ │ ├── JUnitPlatformTestSuite.java
│ │ ├── StackTracePruningTests.java
│ │ ├── TestEngineTests.java
│ │ ├── commons/
│ │ │ ├── annotation/
│ │ │ │ └── TestableAnnotationTests.java
│ │ │ ├── function/
│ │ │ │ └── TryTests.java
│ │ │ ├── support/
│ │ │ │ ├── AnnotationSupportTests.java
│ │ │ │ ├── ClassSupportTests.java
│ │ │ │ ├── ModifierSupportTests.java
│ │ │ │ ├── ReflectionSupportTests.java
│ │ │ │ ├── ResourceInteroperabilityTests.java
│ │ │ │ ├── ResourceSupportTests.java
│ │ │ │ └── conversion/
│ │ │ │ ├── ConversionSupportTests.java
│ │ │ │ └── FallbackStringToObjectConverterTests.java
│ │ │ └── util/
│ │ │ ├── AnnotationUtilsTests.java
│ │ │ ├── ClassLoaderUtilsTests.java
│ │ │ ├── ClassNamePatternFilterUtilsTests.java
│ │ │ ├── ClassUtilsTests.java
│ │ │ ├── CloseablePathTests.java
│ │ │ ├── CollectionUtilsTests.java
│ │ │ ├── DefaultClasspathScannerTests.java
│ │ │ ├── ExceptionUtilsTests.java
│ │ │ ├── FunctionUtilsTests.java
│ │ │ ├── LruCacheTests.java
│ │ │ ├── PackageUtilsTests.java
│ │ │ ├── PreconditionsTests.java
│ │ │ ├── ReflectionUtilsTests.java
│ │ │ ├── ReflectionUtilsWithGenericTypeHierarchiesTests.java
│ │ │ ├── RuntimeUtilsTests.java
│ │ │ ├── SerializationUtils.java
│ │ │ ├── StringUtilsTests.java
│ │ │ ├── ToStringBuilderTests.java
│ │ │ ├── classes/
│ │ │ │ ├── AExecutionConditionClass.java
│ │ │ │ ├── ATestExecutionListenerClass.java
│ │ │ │ ├── AVanillaEmpty.java
│ │ │ │ ├── BExecutionConditionClass.java
│ │ │ │ ├── BTestExecutionListenerClass.java
│ │ │ │ ├── BVanillaEmpty.java
│ │ │ │ └── CustomType.java
│ │ │ └── pkg1/
│ │ │ ├── ClassLevelDir.java
│ │ │ ├── InstanceLevelDir.java
│ │ │ ├── SuperclassWithStaticPackagePrivateBeforeMethod.java
│ │ │ ├── SuperclassWithStaticPackagePrivateTempDirField.java
│ │ │ └── subpkg/
│ │ │ ├── SubclassWithNonStaticPackagePrivateBeforeMethod.java
│ │ │ └── SubclassWithNonStaticPackagePrivateTempDirField.java
│ │ ├── console/
│ │ │ ├── ConsoleDetailsTests.java
│ │ │ ├── ConsoleLauncherIntegrationTests.java
│ │ │ ├── ConsoleLauncherTests.java
│ │ │ ├── ConsoleLauncherWrapper.java
│ │ │ ├── ConsoleLauncherWrapperResult.java
│ │ │ ├── command/
│ │ │ │ ├── CommandLineOptionsParsingTests.java
│ │ │ │ ├── ConsoleTestExecutorTests.java
│ │ │ │ ├── ConsoleUtilsTests.java
│ │ │ │ ├── CustomContextClassLoaderExecutorTests.java
│ │ │ │ ├── DiscoveryRequestCreatorTests.java
│ │ │ │ ├── ExecuteTestsCommandTests.java
│ │ │ │ ├── StdStreamTestCase.java
│ │ │ │ └── ThemeTests.java
│ │ │ ├── output/
│ │ │ │ ├── ColorPaletteTests.java
│ │ │ │ ├── FlatPrintingListenerTests.java
│ │ │ │ ├── TestFeedPrintingListenerTests.java
│ │ │ │ ├── TreeNodeTests.java
│ │ │ │ ├── TreePrinterTests.java
│ │ │ │ └── VerboseTreePrintingListenerTests.java
│ │ │ └── subpackage/
│ │ │ ├── ContainerForInnerTest.java
│ │ │ ├── ContainerForInnerTests.java
│ │ │ ├── ContainerForStaticNestedTest.java
│ │ │ ├── ContainerForStaticNestedTests.java
│ │ │ ├── FailingTestCase.java
│ │ │ ├── SecondTest.java
│ │ │ ├── Test.java
│ │ │ ├── Test1.java
│ │ │ ├── Tests.java
│ │ │ └── ThirdTests.java
│ │ ├── engine/
│ │ │ ├── CompositeTestDescriptorVisitorTests.java
│ │ │ ├── DiscoveryIssueTests.java
│ │ │ ├── FilterCompositionTests.java
│ │ │ ├── TestDescriptorTests.java
│ │ │ ├── TestTagTests.java
│ │ │ ├── UniqueIdFormatTests.java
│ │ │ ├── UniqueIdTests.java
│ │ │ ├── discovery/
│ │ │ │ ├── ClassNameFilterTests.java
│ │ │ │ ├── ClassSelectorTests.java
│ │ │ │ ├── ClasspathResourceSelectorTests.java
│ │ │ │ ├── ClasspathRootSelectorTests.java
│ │ │ │ ├── DirectorySelectorTests.java
│ │ │ │ ├── DiscoverySelectorsTests.java
│ │ │ │ ├── FilePositionTests.java
│ │ │ │ ├── FileSelectorTests.java
│ │ │ │ ├── IterationSelectorTests.java
│ │ │ │ ├── MethodSelectorTests.java
│ │ │ │ ├── ModuleSelectorTests.java
│ │ │ │ ├── NestedClassSelectorTests.java
│ │ │ │ ├── NestedMethodSelectorTests.java
│ │ │ │ ├── PackageNameFilterTests.java
│ │ │ │ ├── PackageSelectorTests.java
│ │ │ │ ├── UniqueIdSelectorTests.java
│ │ │ │ └── UriSelectorTests.java
│ │ │ └── support/
│ │ │ ├── config/
│ │ │ │ └── PrefixedConfigurationParametersTests.java
│ │ │ ├── descriptor/
│ │ │ │ ├── AbstractTestDescriptorTests.java
│ │ │ │ ├── AbstractTestSourceTests.java
│ │ │ │ ├── ClassSourceTests.java
│ │ │ │ ├── ClasspathResourceSourceTests.java
│ │ │ │ ├── CompositeTestSourceTests.java
│ │ │ │ ├── DefaultUriSourceTests.java
│ │ │ │ ├── DemoClassTestDescriptor.java
│ │ │ │ ├── DemoMethodTestDescriptor.java
│ │ │ │ ├── FilePositionTests.java
│ │ │ │ ├── FileSystemSourceTests.java
│ │ │ │ ├── MethodSourceTests.java
│ │ │ │ ├── PackageSourceTests.java
│ │ │ │ ├── TestDescriptorOrderChildrenTests.java
│ │ │ │ └── TestDescriptorTests.java
│ │ │ ├── discovery/
│ │ │ │ ├── EngineDiscoveryRequestResolverTests.java
│ │ │ │ └── ResourceContainerSelectorResolverTest.java
│ │ │ ├── hierarchical/
│ │ │ │ ├── CompositeLockTests.java
│ │ │ │ ├── DefaultParallelExecutionConfigurationStrategyTests.java
│ │ │ │ ├── ForkJoinDeadLockTests.java
│ │ │ │ ├── ForkJoinPoolHierarchicalTestExecutorServiceTests.java
│ │ │ │ ├── HierarchicalTestExecutorTests.java
│ │ │ │ ├── LockManagerTests.java
│ │ │ │ ├── MemoryLeakTests.java
│ │ │ │ ├── NodeTreeWalkerIntegrationTests.java
│ │ │ │ ├── ParallelExecutionIntegrationTests.java
│ │ │ │ ├── ResourceLockSupport.java
│ │ │ │ ├── ResourceLockTests.java
│ │ │ │ ├── SameThreadExecutionIntegrationTests.java
│ │ │ │ ├── SingleLockTests.java
│ │ │ │ ├── ThrowableCollectorTests.java
│ │ │ │ ├── WorkerLeaseManagerTests.java
│ │ │ │ └── WorkerThreadPoolHierarchicalTestExecutorServiceTests.java
│ │ │ └── store/
│ │ │ ├── NamespaceTests.java
│ │ │ └── NamespacedHierarchicalStoreTests.java
│ │ ├── launcher/
│ │ │ ├── DiscoveryFilterStub.java
│ │ │ ├── FilterStub.java
│ │ │ ├── InterceptedTestEngine.java
│ │ │ ├── InterceptorInjectedLauncherSessionListener.java
│ │ │ ├── MethodFilterTests.java
│ │ │ ├── PostDiscoveryFilterStub.java
│ │ │ ├── TagFilterTests.java
│ │ │ ├── TagIntegrationTests.java
│ │ │ ├── TestIdentifierTests.java
│ │ │ ├── TestLauncherDiscoveryListener.java
│ │ │ ├── TestLauncherInterceptor1.java
│ │ │ ├── TestLauncherInterceptor2.java
│ │ │ ├── TestLauncherSessionListener.java
│ │ │ ├── TestPlanTests.java
│ │ │ ├── TestPostDiscoveryTagFilter.java
│ │ │ ├── core/
│ │ │ │ ├── ClasspathAlignmentCheckerTests.java
│ │ │ │ ├── CompositeEngineExecutionListenerTests.java
│ │ │ │ ├── CompositeTestExecutionListenerTests.java
│ │ │ │ ├── DefaultLauncherEngineFilterTests.java
│ │ │ │ ├── DefaultLauncherTests.java
│ │ │ │ ├── DiscoveryIssueCollectorTests.java
│ │ │ │ ├── DiscoveryIssueReportingDiscoveryListenerTests.java
│ │ │ │ ├── EngineDiscoveryResultValidatorTests.java
│ │ │ │ ├── ExecutionListenerAdapterTests.java
│ │ │ │ ├── HierarchicalOutputDirectoryCreatorTests.java
│ │ │ │ ├── InternalTestPlanTests.java
│ │ │ │ ├── LauncherConfigTests.java
│ │ │ │ ├── LauncherConfigurationParametersTests.java
│ │ │ │ ├── LauncherDiscoveryRequestBuilderTests.java
│ │ │ │ ├── LauncherDiscoveryResultTests.java
│ │ │ │ ├── LauncherFactoryTests.java
│ │ │ │ ├── LauncherPreconditionTests.java
│ │ │ │ ├── LauncherSessionTests.java
│ │ │ │ ├── ListenerRegistryTests.java
│ │ │ │ ├── StoreSharingTests.java
│ │ │ │ ├── StreamInterceptingTestExecutionListenerIntegrationTests.java
│ │ │ │ └── StreamInterceptorTests.java
│ │ │ ├── jfr/
│ │ │ │ ├── FlightRecordingDiscoveryListenerIntegrationTests.java
│ │ │ │ └── FlightRecordingExecutionListenerIntegrationTests.java
│ │ │ ├── listeners/
│ │ │ │ ├── AnotherUnusedTestExecutionListener.java
│ │ │ │ ├── LoggingListenerTests.java
│ │ │ │ ├── NoopTestExecutionListener.java
│ │ │ │ ├── OutputDirTests.java
│ │ │ │ ├── SummaryGenerationTests.java
│ │ │ │ ├── UniqueIdTrackingListenerIntegrationTests.java
│ │ │ │ ├── UnusedTestExecutionListener.java
│ │ │ │ ├── discovery/
│ │ │ │ │ ├── AbortOnFailureLauncherDiscoveryListenerTests.java
│ │ │ │ │ ├── CompositeLauncherDiscoveryListenerTests.java
│ │ │ │ │ └── LoggingLauncherDiscoveryListenerTests.java
│ │ │ │ └── session/
│ │ │ │ └── CompositeLauncherSessionListenerTests.java
│ │ │ └── tagexpression/
│ │ │ ├── ParserErrorTests.java
│ │ │ ├── ParserTests.java
│ │ │ ├── TagExpressionsTests.java
│ │ │ ├── TokenTests.java
│ │ │ └── TokenizerTests.java
│ │ ├── reporting/
│ │ │ ├── legacy/
│ │ │ │ └── xml/
│ │ │ │ ├── IncrementingClock.java
│ │ │ │ ├── LegacyXmlReportGeneratingListenerTests.java
│ │ │ │ ├── XmlReportAssertions.java
│ │ │ │ ├── XmlReportDataTests.java
│ │ │ │ └── XmlReportWriterTests.java
│ │ │ └── open/
│ │ │ └── xml/
│ │ │ ├── JUnitContributorTests.java
│ │ │ └── OpenTestReportGeneratingListenerTests.java
│ │ ├── suite/
│ │ │ └── engine/
│ │ │ ├── BeforeAndAfterSuiteTests.java
│ │ │ ├── SuiteEngineTests.java
│ │ │ ├── SuiteLauncherDiscoveryRequestBuilderTests.java
│ │ │ ├── SuiteTestDescriptorTests.java
│ │ │ ├── error/
│ │ │ │ ├── ErrorSelector.java
│ │ │ │ ├── ErrorSelectorIdentifierParser.java
│ │ │ │ ├── SelectorProcessingErrorCausingEngine.java
│ │ │ │ └── package-info.java
│ │ │ ├── testcases/
│ │ │ │ ├── ConfigurationSensitiveTestCase.java
│ │ │ │ ├── DynamicTestsTestCase.java
│ │ │ │ ├── EmptyDynamicTestsTestCase.java
│ │ │ │ ├── EmptyTestTestCase.java
│ │ │ │ ├── ErroneousTestCase.java
│ │ │ │ ├── JUnit4TestsTestCase.java
│ │ │ │ ├── MultipleTestsTestCase.java
│ │ │ │ ├── SingleTestTestCase.java
│ │ │ │ ├── StatefulTestCase.java
│ │ │ │ └── TaggedTestTestCase.java
│ │ │ └── testsuites/
│ │ │ ├── AbstractSuite.java
│ │ │ ├── BlankSuiteDisplayNameSuite.java
│ │ │ ├── ConfigurationSuite.java
│ │ │ ├── CyclicSuite.java
│ │ │ ├── DynamicSuite.java
│ │ │ ├── EmptyCyclicSuite.java
│ │ │ ├── EmptyDynamicTestSuite.java
│ │ │ ├── EmptyDynamicTestWithFailIfNoTestFalseSuite.java
│ │ │ ├── EmptyTestCaseSuite.java
│ │ │ ├── EmptyTestCaseWithFailIfNoTestFalseSuite.java
│ │ │ ├── ErroneousTestSuite.java
│ │ │ ├── InheritedSuite.java
│ │ │ ├── LifecycleMethodsSuites.java
│ │ │ ├── MultiEngineSuite.java
│ │ │ ├── MultipleSuite.java
│ │ │ ├── NestedSuite.java
│ │ │ ├── SelectByIdentifierSuite.java
│ │ │ ├── SelectClassesSuite.java
│ │ │ ├── SelectMethodsSuite.java
│ │ │ ├── SelectorProcessingErrorTestSuite.java
│ │ │ ├── SuiteDisplayNameSuite.java
│ │ │ ├── SuiteSuite.java
│ │ │ ├── SuiteWithErroneousTestSuite.java
│ │ │ ├── ThreePartCyclicSuite.java
│ │ │ └── WhitespaceSuiteDisplayNameSuite.java
│ │ └── testkit/
│ │ └── engine/
│ │ ├── EngineDiscoveryResultsIntegrationTests.java
│ │ ├── EngineTestKitTests.java
│ │ ├── EventsTests.java
│ │ ├── ExecutionsIntegrationTests.java
│ │ ├── NestedContainerEventConditionTests.java
│ │ └── TestExecutionResultConditionsTests.java
│ ├── kotlin/
│ │ └── org/
│ │ └── junit/
│ │ └── platform/
│ │ └── commons/
│ │ └── util/
│ │ └── KotlinReflectionUtilsTests.kt
│ └── resources/
│ ├── META-INF/
│ │ └── services/
│ │ └── org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser
│ ├── config-test-override.properties
│ ├── config-test.properties
│ ├── console/
│ │ └── details/
│ │ ├── basic/
│ │ │ ├── Basic-changeDisplayName-flat-ascii.out.txt
│ │ │ ├── Basic-changeDisplayName-flat-unicode.out.txt
│ │ │ ├── Basic-changeDisplayName-none-ascii.out.txt
│ │ │ ├── Basic-changeDisplayName-none-unicode.out.txt
│ │ │ ├── Basic-changeDisplayName-summary-ascii.out.txt
│ │ │ ├── Basic-changeDisplayName-summary-unicode.out.txt
│ │ │ ├── Basic-changeDisplayName-testfeed-ascii.out.txt
│ │ │ ├── Basic-changeDisplayName-testfeed-unicode.out.txt
│ │ │ ├── Basic-changeDisplayName-tree-ascii.out.txt
│ │ │ ├── Basic-changeDisplayName-tree-unicode.out.txt
│ │ │ ├── Basic-changeDisplayName-verbose-ascii.out.txt
│ │ │ ├── Basic-changeDisplayName-verbose-unicode.out.txt
│ │ │ ├── Basic-empty-flat-ascii.out.txt
│ │ │ ├── Basic-empty-flat-unicode.out.txt
│ │ │ ├── Basic-empty-none-ascii.out.txt
│ │ │ ├── Basic-empty-none-unicode.out.txt
│ │ │ ├── Basic-empty-summary-ascii.out.txt
│ │ │ ├── Basic-empty-summary-unicode.out.txt
│ │ │ ├── Basic-empty-testfeed-ascii.out.txt
│ │ │ ├── Basic-empty-testfeed-unicode.out.txt
│ │ │ ├── Basic-empty-tree-ascii.out.txt
│ │ │ ├── Basic-empty-tree-unicode.out.txt
│ │ │ ├── Basic-empty-verbose-ascii.out.txt
│ │ │ └── Basic-empty-verbose-unicode.out.txt
│ │ ├── fail/
│ │ │ ├── Fail-failWithMultiLineMessage-flat-ascii.out.txt
│ │ │ ├── Fail-failWithMultiLineMessage-flat-unicode.out.txt
│ │ │ ├── Fail-failWithMultiLineMessage-none-ascii.out.txt
│ │ │ ├── Fail-failWithMultiLineMessage-none-unicode.out.txt
│ │ │ ├── Fail-failWithMultiLineMessage-summary-ascii.out.txt
│ │ │ ├── Fail-failWithMultiLineMessage-summary-unicode.out.txt
│ │ │ ├── Fail-failWithMultiLineMessage-testfeed-ascii.out.txt
│ │ │ ├── Fail-failWithMultiLineMessage-testfeed-unicode.out.txt
│ │ │ ├── Fail-failWithMultiLineMessage-tree-ascii.out.txt
│ │ │ ├── Fail-failWithMultiLineMessage-tree-unicode.out.txt
│ │ │ ├── Fail-failWithMultiLineMessage-verbose-ascii.out.txt
│ │ │ ├── Fail-failWithMultiLineMessage-verbose-unicode.out.txt
│ │ │ ├── Fail-failWithSingleLineMessage-flat-ascii.out.txt
│ │ │ ├── Fail-failWithSingleLineMessage-flat-unicode.out.txt
│ │ │ ├── Fail-failWithSingleLineMessage-none-ascii.out.txt
│ │ │ ├── Fail-failWithSingleLineMessage-none-unicode.out.txt
│ │ │ ├── Fail-failWithSingleLineMessage-summary-ascii.out.txt
│ │ │ ├── Fail-failWithSingleLineMessage-summary-unicode.out.txt
│ │ │ ├── Fail-failWithSingleLineMessage-testfeed-ascii.out.txt
│ │ │ ├── Fail-failWithSingleLineMessage-testfeed-unicode.out.txt
│ │ │ ├── Fail-failWithSingleLineMessage-tree-ascii.out.txt
│ │ │ ├── Fail-failWithSingleLineMessage-tree-unicode.out.txt
│ │ │ ├── Fail-failWithSingleLineMessage-verbose-ascii.out.txt
│ │ │ └── Fail-failWithSingleLineMessage-verbose-unicode.out.txt
│ │ ├── report/
│ │ │ ├── Report-reportMultiEntriesWithMultiMappings-flat-ascii.out.txt
│ │ │ ├── Report-reportMultiEntriesWithMultiMappings-flat-unicode.out.txt
│ │ │ ├── Report-reportMultiEntriesWithMultiMappings-none-ascii.out.txt
│ │ │ ├── Report-reportMultiEntriesWithMultiMappings-none-unicode.out.txt
│ │ │ ├── Report-reportMultiEntriesWithMultiMappings-summary-ascii.out.txt
│ │ │ ├── Report-reportMultiEntriesWithMultiMappings-summary-unicode.out.txt
│ │ │ ├── Report-reportMultiEntriesWithMultiMappings-testfeed-ascii.out.txt
│ │ │ ├── Report-reportMultiEntriesWithMultiMappings-testfeed-unicode.out.txt
│ │ │ ├── Report-reportMultiEntriesWithMultiMappings-tree-ascii.out.txt
│ │ │ ├── Report-reportMultiEntriesWithMultiMappings-tree-unicode.out.txt
│ │ │ ├── Report-reportMultiEntriesWithMultiMappings-verbose-ascii.out.txt
│ │ │ ├── Report-reportMultiEntriesWithMultiMappings-verbose-unicode.out.txt
│ │ │ ├── Report-reportMultiEntriesWithSingleMapping-flat-ascii.out.txt
│ │ │ ├── Report-reportMultiEntriesWithSingleMapping-flat-unicode.out.txt
│ │ │ ├── Report-reportMultiEntriesWithSingleMapping-none-ascii.out.txt
│ │ │ ├── Report-reportMultiEntriesWithSingleMapping-none-unicode.out.txt
│ │ │ ├── Report-reportMultiEntriesWithSingleMapping-summary-ascii.out.txt
│ │ │ ├── Report-reportMultiEntriesWithSingleMapping-summary-unicode.out.txt
│ │ │ ├── Report-reportMultiEntriesWithSingleMapping-testfeed-ascii.out.txt
│ │ │ ├── Report-reportMultiEntriesWithSingleMapping-testfeed-unicode.out.txt
│ │ │ ├── Report-reportMultiEntriesWithSingleMapping-tree-ascii.out.txt
│ │ │ ├── Report-reportMultiEntriesWithSingleMapping-tree-unicode.out.txt
│ │ │ ├── Report-reportMultiEntriesWithSingleMapping-verbose-ascii.out.txt
│ │ │ ├── Report-reportMultiEntriesWithSingleMapping-verbose-unicode.out.txt
│ │ │ ├── Report-reportMultipleMessages-flat-ascii.out.txt
│ │ │ ├── Report-reportMultipleMessages-flat-unicode.out.txt
│ │ │ ├── Report-reportMultipleMessages-none-ascii.out.txt
│ │ │ ├── Report-reportMultipleMessages-none-unicode.out.txt
│ │ │ ├── Report-reportMultipleMessages-summary-ascii.out.txt
│ │ │ ├── Report-reportMultipleMessages-summary-unicode.out.txt
│ │ │ ├── Report-reportMultipleMessages-testfeed-ascii.out.txt
│ │ │ ├── Report-reportMultipleMessages-testfeed-unicode.out.txt
│ │ │ ├── Report-reportMultipleMessages-tree-ascii.out.txt
│ │ │ ├── Report-reportMultipleMessages-tree-unicode.out.txt
│ │ │ ├── Report-reportMultipleMessages-verbose-ascii.out.txt
│ │ │ ├── Report-reportMultipleMessages-verbose-unicode.out.txt
│ │ │ ├── Report-reportSingleEntryWithSingleMapping-flat-ascii.out.txt
│ │ │ ├── Report-reportSingleEntryWithSingleMapping-flat-unicode.out.txt
│ │ │ ├── Report-reportSingleEntryWithSingleMapping-none-ascii.out.txt
│ │ │ ├── Report-reportSingleEntryWithSingleMapping-none-unicode.out.txt
│ │ │ ├── Report-reportSingleEntryWithSingleMapping-summary-ascii.out.txt
│ │ │ ├── Report-reportSingleEntryWithSingleMapping-summary-unicode.out.txt
│ │ │ ├── Report-reportSingleEntryWithSingleMapping-testfeed-ascii.out.txt
│ │ │ ├── Report-reportSingleEntryWithSingleMapping-testfeed-unicode.out.txt
│ │ │ ├── Report-reportSingleEntryWithSingleMapping-tree-ascii.out.txt
│ │ │ ├── Report-reportSingleEntryWithSingleMapping-tree-unicode.out.txt
│ │ │ ├── Report-reportSingleEntryWithSingleMapping-verbose-ascii.out.txt
│ │ │ ├── Report-reportSingleEntryWithSingleMapping-verbose-unicode.out.txt
│ │ │ ├── Report-reportSingleMessage-flat-ascii.out.txt
│ │ │ ├── Report-reportSingleMessage-flat-unicode.out.txt
│ │ │ ├── Report-reportSingleMessage-none-ascii.out.txt
│ │ │ ├── Report-reportSingleMessage-none-unicode.out.txt
│ │ │ ├── Report-reportSingleMessage-summary-ascii.out.txt
│ │ │ ├── Report-reportSingleMessage-summary-unicode.out.txt
│ │ │ ├── Report-reportSingleMessage-testfeed-ascii.out.txt
│ │ │ ├── Report-reportSingleMessage-testfeed-unicode.out.txt
│ │ │ ├── Report-reportSingleMessage-tree-ascii.out.txt
│ │ │ ├── Report-reportSingleMessage-tree-unicode.out.txt
│ │ │ ├── Report-reportSingleMessage-verbose-ascii.out.txt
│ │ │ └── Report-reportSingleMessage-verbose-unicode.out.txt
│ │ └── skip/
│ │ ├── Skip-skipWithMultiLineMessage-flat-ascii.out.txt
│ │ ├── Skip-skipWithMultiLineMessage-flat-unicode.out.txt
│ │ ├── Skip-skipWithMultiLineMessage-none-ascii.out.txt
│ │ ├── Skip-skipWithMultiLineMessage-none-unicode.out.txt
│ │ ├── Skip-skipWithMultiLineMessage-summary-ascii.out.txt
│ │ ├── Skip-skipWithMultiLineMessage-summary-unicode.out.txt
│ │ ├── Skip-skipWithMultiLineMessage-testfeed-ascii.out.txt
│ │ ├── Skip-skipWithMultiLineMessage-testfeed-unicode.out.txt
│ │ ├── Skip-skipWithMultiLineMessage-tree-ascii.out.txt
│ │ ├── Skip-skipWithMultiLineMessage-tree-unicode.out.txt
│ │ ├── Skip-skipWithMultiLineMessage-verbose-ascii.out.txt
│ │ ├── Skip-skipWithMultiLineMessage-verbose-unicode.out.txt
│ │ ├── Skip-skipWithSingleLineReason-flat-ascii.out.txt
│ │ ├── Skip-skipWithSingleLineReason-flat-unicode.out.txt
│ │ ├── Skip-skipWithSingleLineReason-none-ascii.out.txt
│ │ ├── Skip-skipWithSingleLineReason-none-unicode.out.txt
│ │ ├── Skip-skipWithSingleLineReason-summary-ascii.out.txt
│ │ ├── Skip-skipWithSingleLineReason-summary-unicode.out.txt
│ │ ├── Skip-skipWithSingleLineReason-testfeed-ascii.out.txt
│ │ ├── Skip-skipWithSingleLineReason-testfeed-unicode.out.txt
│ │ ├── Skip-skipWithSingleLineReason-tree-ascii.out.txt
│ │ ├── Skip-skipWithSingleLineReason-tree-unicode.out.txt
│ │ ├── Skip-skipWithSingleLineReason-verbose-ascii.out.txt
│ │ └── Skip-skipWithSingleLineReason-verbose-unicode.out.txt
│ ├── default-package.resource
│ ├── do_not_delete_me.txt
│ ├── error-engine/
│ │ └── META-INF/
│ │ └── services/
│ │ └── org.junit.platform.engine.TestEngine
│ ├── folder with spaces/
│ │ └── jar test with spaces.jar
│ ├── gh-1436-invalid-nested-class-file.jar
│ ├── intercepted-testservices/
│ │ └── META-INF/
│ │ └── services/
│ │ ├── org.junit.platform.engine.TestEngine
│ │ └── org.junit.platform.launcher.LauncherSessionListener
│ ├── jartest-shadowed.jar
│ ├── jartest.jar
│ ├── jenkins-junit.xsd
│ ├── junit-platform.properties
│ ├── log4j2-test.xml
│ ├── modules-2500/
│ │ ├── foo/
│ │ │ ├── Foo.java
│ │ │ └── module-info.java
│ │ └── foo.bar/
│ │ ├── FooBar.java
│ │ └── module-info.java
│ ├── org/
│ │ └── junit/
│ │ └── platform/
│ │ └── commons/
│ │ ├── example.resource
│ │ └── other-example.resource
│ ├── test-junit-platform.properties
│ └── testservices/
│ └── META-INF/
│ └── services/
│ ├── org.junit.platform.launcher.LauncherDiscoveryListener
│ ├── org.junit.platform.launcher.LauncherInterceptor
│ ├── org.junit.platform.launcher.LauncherSessionListener
│ ├── org.junit.platform.launcher.PostDiscoveryFilter
│ └── org.junit.platform.launcher.TestExecutionListener
├── platform-tooling-support-tests/
│ ├── platform-tooling-support-tests.gradle.kts
│ ├── projects/
│ │ ├── graalvm-starter/
│ │ │ ├── build.gradle.kts
│ │ │ ├── settings.gradle.kts
│ │ │ └── src/
│ │ │ ├── main/
│ │ │ │ └── java/
│ │ │ │ └── com/
│ │ │ │ └── example/
│ │ │ │ └── project/
│ │ │ │ └── Calculator.java
│ │ │ └── test/
│ │ │ ├── java/
│ │ │ │ └── com/
│ │ │ │ └── example/
│ │ │ │ └── project/
│ │ │ │ ├── CalculatorParameterizedClassTests.java
│ │ │ │ ├── CalculatorTests.java
│ │ │ │ ├── ClassLevelAnnotationTests.java
│ │ │ │ ├── GraalvmSuite.java
│ │ │ │ └── VintageTests.java
│ │ │ └── resources/
│ │ │ └── junit-platform.properties
│ │ ├── gradle-kotlin-extensions/
│ │ │ ├── build.gradle.kts
│ │ │ ├── gradle.properties
│ │ │ ├── settings.gradle.kts
│ │ │ └── src/
│ │ │ └── test/
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── project/
│ │ │ └── ExtensionFunctionsTests.kt
│ │ ├── gradle-missing-engine/
│ │ │ ├── build.gradle.kts
│ │ │ ├── gradle.properties
│ │ │ ├── settings.gradle.kts
│ │ │ └── src/
│ │ │ └── test/
│ │ │ └── java/
│ │ │ └── FooTests.java
│ │ ├── jar-describe-module/
│ │ │ ├── junit-jupiter-api.expected.txt
│ │ │ ├── junit-jupiter-engine.expected.txt
│ │ │ ├── junit-jupiter-migrationsupport.expected.txt
│ │ │ ├── junit-jupiter-params.expected.txt
│ │ │ ├── junit-jupiter.expected.txt
│ │ │ ├── junit-platform-commons.expected.txt
│ │ │ ├── junit-platform-console.expected.txt
│ │ │ ├── junit-platform-engine.expected.txt
│ │ │ ├── junit-platform-launcher.expected.txt
│ │ │ ├── junit-platform-reporting.expected.txt
│ │ │ ├── junit-platform-suite-api.expected.txt
│ │ │ ├── junit-platform-suite-engine.expected.txt
│ │ │ ├── junit-platform-suite.expected.txt
│ │ │ ├── junit-platform-testkit.expected.txt
│ │ │ ├── junit-start.expected.txt
│ │ │ └── junit-vintage-engine.expected.txt
│ │ ├── junit-start/
│ │ │ ├── compact/
│ │ │ │ ├── JUnitRun.java
│ │ │ │ └── JUnitRunClass.java
│ │ │ └── modular/
│ │ │ ├── module-info.java
│ │ │ └── p/
│ │ │ ├── JUnitRunModule.java
│ │ │ └── MultiplicationTests.java
│ │ ├── jupiter-starter/
│ │ │ ├── build.gradle.kts
│ │ │ ├── build.xml
│ │ │ ├── gradle.properties
│ │ │ ├── pom.xml
│ │ │ ├── settings.gradle.kts
│ │ │ └── src/
│ │ │ ├── main/
│ │ │ │ └── java/
│ │ │ │ └── com/
│ │ │ │ └── example/
│ │ │ │ └── project/
│ │ │ │ └── Calculator.java
│ │ │ └── test/
│ │ │ ├── java/
│ │ │ │ └── com/
│ │ │ │ └── example/
│ │ │ │ └── project/
│ │ │ │ ├── CalculatorParameterizedClassTests.java
│ │ │ │ └── CalculatorTests.java
│ │ │ └── resources/
│ │ │ └── junit-platform.properties
│ │ ├── kotlin-coroutines/
│ │ │ ├── build.gradle.kts
│ │ │ ├── settings.gradle.kts
│ │ │ └── src/
│ │ │ └── test/
│ │ │ └── kotlin/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── project/
│ │ │ └── SuspendFunctionTests.kt
│ │ ├── maven-surefire-compatibility/
│ │ │ ├── pom.xml
│ │ │ └── src/
│ │ │ └── test/
│ │ │ └── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── project/
│ │ │ └── DummyTests.java
│ │ ├── memory-cleanup/
│ │ │ └── src/
│ │ │ └── OneMillionTests.java
│ │ ├── reflection-tests/
│ │ │ ├── build.gradle.kts
│ │ │ ├── gradle.properties
│ │ │ ├── settings.gradle.kts
│ │ │ └── src/
│ │ │ └── test/
│ │ │ └── java/
│ │ │ └── ReflectionTestCase.java
│ │ ├── standalone/
│ │ │ ├── expected-err.txt
│ │ │ ├── expected-out.txt
│ │ │ ├── logging.properties
│ │ │ └── src/
│ │ │ ├── other/
│ │ │ │ └── OtherwiseNotReferencedClass.java
│ │ │ └── standalone/
│ │ │ ├── JupiterIntegration.java
│ │ │ ├── JupiterParamsIntegration.java
│ │ │ ├── SuiteIntegration.java
│ │ │ └── VintageIntegration.java
│ │ └── vintage/
│ │ ├── build.gradle.kts
│ │ ├── gradle.properties
│ │ ├── pom.xml
│ │ ├── settings.gradle.kts
│ │ └── src/
│ │ └── test/
│ │ └── java/
│ │ ├── DefaultPackageTest.java
│ │ └── com/
│ │ └── example/
│ │ └── vintage/
│ │ └── VintageTest.java
│ └── src/
│ ├── archUnit/
│ │ └── java/
│ │ └── platform/
│ │ └── tooling/
│ │ └── support/
│ │ └── tests/
│ │ └── ArchUnitTests.java
│ ├── main/
│ │ └── java/
│ │ └── platform/
│ │ └── tooling/
│ │ └── support/
│ │ ├── Helper.java
│ │ ├── MavenRepo.java
│ │ ├── ProcessStarters.java
│ │ ├── ThirdPartyJars.java
│ │ └── package-info.java
│ └── test/
│ ├── java/
│ │ └── platform/
│ │ └── tooling/
│ │ └── support/
│ │ ├── HelperTests.java
│ │ └── tests/
│ │ ├── AntStarterTests.java
│ │ ├── FilePrefix.java
│ │ ├── GraalVmStarterTests.java
│ │ ├── GradleKotlinExtensionsTests.java
│ │ ├── GradleMissingEngineTests.java
│ │ ├── GradleModuleFileTests.java
│ │ ├── GradleStarterTests.java
│ │ ├── JUnitStartTests.java
│ │ ├── JarContainsManifestFirstTests.java
│ │ ├── JarDescribeModuleTests.java
│ │ ├── KotlinCoroutinesTests.java
│ │ ├── LocalMavenRepo.java
│ │ ├── ManagedResource.java
│ │ ├── ManifestTests.java
│ │ ├── MavenEnvVars.java
│ │ ├── MavenPomFileTests.java
│ │ ├── MavenRepoProxy.java
│ │ ├── MavenStarterTests.java
│ │ ├── MavenSurefireCompatibilityTests.java
│ │ ├── MemoryCleanupTests.java
│ │ ├── ModularCompilationTests.java
│ │ ├── ModularUserGuideTests.java
│ │ ├── OutputAttachingExtension.java
│ │ ├── Projects.java
│ │ ├── ReflectionCompatibilityTests.java
│ │ ├── StandaloneTests.java
│ │ ├── ToolProviderTests.java
│ │ ├── UnalignedClasspathTests.java
│ │ ├── VintageGradleIntegrationTests.java
│ │ ├── VintageMavenIntegrationTests.java
│ │ └── XmlAssertions.java
│ └── resources/
│ ├── junit-platform.properties
│ ├── log4j2-test.xml
│ └── platform/
│ └── tooling/
│ └── support/
│ └── tests/
│ ├── AntStarterTests_snapshots/
│ │ └── open-test-report.xml.snapshot
│ ├── GradleStarterTests_snapshots/
│ │ └── open-test-report.xml.snapshot
│ └── MavenStarterTests_snapshots/
│ └── open-test-report.xml.snapshot
└── settings.gradle.kts
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.bat]
end_of_line = crlf
[*.md]
# Trailing whitespace is important in Markdown (they distinguish a new line from a new paragraph)
trim_trailing_whitespace = false
[*.{kt,kts}]
ij_kotlin_allow_trailing_comma = false
ij_kotlin_allow_trailing_comma_on_call_site = false
[*.kts]
indent_style = tab
================================================
FILE: .gitattributes
================================================
* text eol=lf
*.bat text eol=crlf
*.png binary
*.key binary
*.jar binary
*.ttf binary
release-notes-* merge=union
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Report a bug or regression
about: Create a report to help us improve
type: Bug
labels: ["type: bug"]
---
## Steps to reproduce
## Context
- Used versions (Jupiter/Vintage/Platform):
- Build Tool/IDE:
## Deliverables
- [ ] ...
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Ask a question
url: https://github.com/junit-team/junit-framework/discussions/categories/q-a
about: Please ask and answer questions here
- name: Ask a question (Stack Overflow)
url: https://stackoverflow.com/questions/tagged/junit5
about: Alternatively, ask questions here
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Request a feature or enhancement
about: Suggest an idea for this project
type: Feature
labels: ["type: new feature"]
---
## Deliverables
- [ ] ...
================================================
FILE: .github/ISSUE_TEMPLATE/task.md
================================================
---
name: Create a task
about: Create a task for a specific piece of work
type: Task
labels: ["type: task"]
---
## Deliverables
- [ ] ...
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
---
I hereby agree to the terms of the [JUnit Contributor License Agreement](https://github.com/junit-team/junit-framework/blob/002a0052926ddee57cf90580fa49bc37e5a72427/CONTRIBUTING.md#junit-contributor-license-agreement).
---
### Definition of Done
- [ ] There are no TODOs left in the code
- [ ] Method [preconditions](https://docs.junit.org/snapshot/api/org.junit.platform.commons/org/junit/platform/commons/util/Preconditions.html) are checked and documented in the method's Javadoc
- [ ] [Coding conventions](https://github.com/junit-team/junit-framework/blob/HEAD/CONTRIBUTING.md#coding-conventions) (e.g. for logging) have been followed
- [ ] Change is covered by [automated tests](https://github.com/junit-team/junit-framework/blob/HEAD/CONTRIBUTING.md#tests) including corner cases, errors, and exception handling
- [ ] Public API has [Javadoc](https://github.com/junit-team/junit-framework/blob/HEAD/CONTRIBUTING.md#javadoc) and [`@API` annotations](https://apiguardian-team.github.io/apiguardian/docs/current/api/org/apiguardian/api/API.html)
- [ ] Change is documented in the [User Guide](https://docs.junit.org/snapshot/) and [Release Notes](https://docs.junit.org/snapshot/release-notes.html)
================================================
FILE: .github/actions/main-build/action.yml
================================================
name: Main build
description: Sets up JDKs and runs Gradle
inputs:
arguments:
required: true
description: Gradle arguments
default: :platform-tooling-support-tests:test build eclipse --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318
encryptionKey:
required: true
description: Gradle cache encryption key
runs:
using: "composite"
steps:
- uses: ./.github/actions/setup-test-jdk
- uses: ./.github/actions/run-gradle
with:
arguments: ${{ inputs.arguments }}
encryptionKey: ${{ inputs.encryptionKey }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ always() }}
with:
name: Open Test Reports (${{ github.job }})
path: '**/build/reports/open-test-report.html'
================================================
FILE: .github/actions/maven-central-user-token/action.yml
================================================
name: Prepare Maven Central user token
description: Compute the Maven Central user token from username and password
inputs:
username:
required: true
description: Maven Central username
password:
required: true
description: Maven Central password
runs:
using: "composite"
steps:
- shell: bash
run: | # zizmor: ignore[github-env]
USER_TOKEN=$(printf "${USERNAME}:${PASSWORD}" | base64)
echo "::add-mask::$USER_TOKEN"
echo "MAVEN_CENTRAL_USER_TOKEN=$USER_TOKEN" >> $GITHUB_ENV
env:
USERNAME: ${{ inputs.username }}
PASSWORD: ${{ inputs.password }}
================================================
FILE: .github/actions/run-gradle/action.yml
================================================
name: Run Gradle
description: Sets up Gradle JDKs and runs Gradle
inputs:
arguments:
required: true
description: Gradle arguments
default: build
broken-by-issue:
required: false
description: Expect the build to fail due the referenced GitHub issue
default: ""
encryptionKey:
required: true
description: Gradle cache encryption key
runs:
using: "composite"
steps:
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
id: setup-gradle-jdk
with:
distribution: temurin
java-version: 25
check-latest: true
- uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
with:
cache-encryption-key: ${{ inputs.encryptionKey }}
- uses: testlens-app/setup-testlens@d96a555133c275a00949d2cc77b70fe9a4242ebf # v1.9.2
with:
write-log-files: true
- shell: bash
env:
JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }}
run: | # zizmor: ignore[template-injection]
./gradlew \
-Dorg.gradle.java.installations.auto-download=false \
-Pjunit.develocity.buildCache.pushEnabled=${{ github.repository == 'junit-team/junit-framework' }} \
-Pjunit.develocity.predictiveTestSelection.enabled=true \
-Pjunit.develocity.predictiveTestSelection.selectRemainingTests=${{ github.event_name != 'pull_request' }} \
"-Dscan.value.GitHub job=${{ github.job }}" \
javaToolchains \
${{ inputs.arguments }} || error_code=$?
if [ -n "${{ inputs.broken-by-issue }}" ]; then
if [ -n "$error_code" ]; then
# we expected the build to fail, it failed => pass
exit 0
else
# we expected the build to fail, it didn't => fail
RED='\033[0;31m'
RESET='\033[0m' # No Color
echo "${RED}Build was expected to fail, but it passed. Was ${{ inputs.broken-by-issue }} resolved?${RESET}"
exit 1
fi
fi
# exit with the original error code if set
if [ -n "$error_code" ]; then
exit "$error_code"
fi
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ always() }}
with:
name: TestLens logs (${{ github.job }} - ${{ job.check_run_id }})
path: '**/build/**/testlens-logs/**'
if-no-files-found: ignore
================================================
FILE: .github/actions/setup-test-jdk/action.yml
================================================
name: Set up Test JDK
description: Sets up the JDK required to run platform-tooling-support-tests
inputs:
distribution:
required: true
description: 'The JDK distribution to use'
default: 'temurin'
runs:
using: "composite"
steps:
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ inputs.distribution }}
java-version: 17
check-latest: true
- shell: bash
run: echo "JDK17=$JAVA_HOME" >> $GITHUB_ENV # zizmor: ignore[github-env]
================================================
FILE: .github/codecov.yml
================================================
comment: false
coverage:
status:
project:
default:
threshold: "1"
informational: true
patch: off
codecov:
max_report_age: off
================================================
FILE: .github/dco.yml
================================================
require:
members: false
================================================
FILE: .github/renovate.json5
================================================
{
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
extends: [
'github>junit-team/renovate-config',
],
baseBranchPatterns: [
'main',
'docs-site'
],
packageRules: [
{
matchCurrentValue: '/^2\\./',
allowedVersions: '(,3.0)',
matchPackageNames: [
'org.codehaus.groovy:{/,}**',
],
},
{
matchCurrentValue: '/^1\\./',
allowedVersions: '/^1\\..*-groovy-2\\.*/',
matchPackageNames: [
'org.spockframework:{/,}**',
],
},
{
allowedVersions: '!/-SNAPSHOT$/',
matchPackageNames: [
'org.opentest4j.reporting:{/,}**',
],
},
{
matchManagers: 'npm',
matchPackageNames: [
'@antora/assembler',
'@antora/html-single-extension',
'@antora/pdf-extension',
],
"groupName": "@antora/assembler",
},
],
}
================================================
FILE: .github/scripts/checkBuildReproducibility.sh
================================================
#!/bin/bash -e
rm -f checksums-1.txt checksums-2.txt
SOURCE_DATE_EPOCH=$(date +%s)
export SOURCE_DATE_EPOCH
function calculate_checksums() {
OUTPUT=$1
./gradlew \
--configuration-cache \
--no-build-cache \
-Dorg.gradle.java.installations.auto-download=false \
-Dscan.tag.Reproducibility \
clean \
assemble
find . -name '*.jar' \
| grep '/build/libs/' \
| grep --invert-match 'javadoc' \
| sort \
| xargs sha512sum > "${OUTPUT}"
}
calculate_checksums checksums-1.txt
calculate_checksums checksums-2.txt
diff checksums-1.txt checksums-2.txt
================================================
FILE: .github/scripts/close-github-milestone.js
================================================
module.exports = async ({ github, context }) => {
const releaseVersion = process.env.RELEASE_VERSION;
const query = `
query ($owner: String!, $repo: String!, $title: String!) {
repository(owner: $owner, name: $repo) {
milestones(first: 100, query: $title) {
nodes {
title
number
openIssueCount
}
}
}
}
`;
const {repository} = await github.graphql(query, {
owner: context.repo.owner,
repo: context.repo.repo,
title: releaseVersion
});
const [milestone] = repository.milestones.nodes.filter(it => it.title === releaseVersion);
if (!milestone) {
throw new Error(`Milestone "${releaseVersion}" not found`);
}
if (milestone.openIssueCount > 0) {
throw new Error(`Milestone "${releaseVersion}" has ${milestone.openIssueCount} open issue(s)`);
}
const requestBody = {
owner: context.repo.owner,
repo: context.repo.repo,
milestone_number: milestone.number,
state: 'closed',
due_on: new Date().toISOString()
};
console.log(requestBody);
await github.rest.issues.updateMilestone(requestBody);
};
================================================
FILE: .github/scripts/create-github-release.js
================================================
module.exports = async ({ github, context }) => {
const releaseVersion = process.env.RELEASE_VERSION;
const requestBody = {
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: `r${releaseVersion}`,
name: `JUnit ${releaseVersion}`,
generate_release_notes: true,
body: `JUnit ${releaseVersion} = Platform ${releaseVersion} + Jupiter ${releaseVersion} + Vintage ${releaseVersion}\n\nSee [Release Notes](https://docs.junit.org/${releaseVersion}/release-notes.html).`,
prerelease: releaseVersion.includes("-"),
};
console.log(requestBody);
await github.rest.repos.createRelease(requestBody);
};
================================================
FILE: .github/scripts/waitForUrl.sh
================================================
#!/usr/bin/env bash
URL=$1
printf 'Waiting for %s' "$URL"
until curl --output /dev/null --silent --location --head --fail "$URL"; do
printf '.'
sleep 5
done
echo ' OK'
================================================
FILE: .github/workflows/close-inactive-issues.yml
================================================
name: Close inactive issues and PRs
on:
schedule:
- cron: "30 1 * * *"
workflow_dispatch:
permissions: {}
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
with:
only-labels: "status: waiting-for-feedback"
days-before-stale: 14
days-before-close: 21
stale-issue-label: "status: stale"
stale-pr-label: "status: stale"
stale-issue-message: >
If you would like us to be able to process this issue, please provide the requested information.
If the information is not provided within the next 3 weeks, we will be unable to proceed and this issue will be closed.
close-issue-message: >
Closing due to lack of requested feedback.
If you would like to proceed with your contribution, please provide the requested information and we will re-open this issue.
stale-pr-message: >
If you would like us to be able to process this pull request, please provide the requested information or make the requested changes.
If the information is not provided or the requested changes are not made within the next 3 weeks, we will be unable to proceed and this pull request will be closed.
close-pr-message: >
Closing due to lack of requested feedback.
If you would like to proceed with your contribution, please provide the requested information or make the requested changes, and we will re-open this pull request.
================================================
FILE: .github/workflows/codeql.yml
================================================
name: "CodeQL"
on:
push:
branches:
- main
- 'releases/**'
pull_request:
# The branches below must be a subset of the branches above
branches:
- main
- 'releases/**'
schedule:
- cron: '0 19 * * 3'
concurrency:
# Cancels in-progress runs only for pull requests
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions: {}
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
security-events: write
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
- language: java-kotlin
build-mode: manual
- language: javascript
build-mode: none
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
tools: linked
- name: Build
if: matrix.build-mode == 'manual'
uses: ./.github/actions/run-gradle
with:
encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
arguments: >-
--no-build-cache
-Dscan.tag.CodeQL
classes
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
category: "/language:${{matrix.language}}"
================================================
FILE: .github/workflows/cross-version.yml
================================================
name: Cross-Version
on:
schedule:
- cron: '0 0 * * 6' # Every Saturday at 00:00 UTC
push:
branches:
- main
- 'releases/**'
pull_request:
branches:
- '**'
concurrency:
# Cancels in-progress runs only for pull requests
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions: {}
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
jobs:
openjdk:
strategy:
fail-fast: false
matrix:
jdk:
- version: 26
type: ga
- version: 27
type: ea
- version: 26
type: ea
release: leyden
- version: 27
type: ea
release: valhalla
name: "OpenJDK ${{ matrix.jdk.version }} (${{ matrix.jdk.release || matrix.jdk.type }})"
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Set up Test JDK
uses: ./.github/actions/setup-test-jdk
- name: "Set up JDK ${{ matrix.jdk.version }} (${{ matrix.jdk.release || 'ea' }})"
if: matrix.jdk.type == 'ea'
uses: oracle-actions/setup-java@fff43251af9936a0e6a4d5d0946e14f1680e9b6b # v1.5.0
with:
website: jdk.java.net
release: ${{ matrix.jdk.release || matrix.jdk.version }}
version: latest
- name: "Set up JDK ${{ matrix.jdk.version }} (${{ matrix.jdk.distribution || 'temurin' }})"
if: matrix.jdk.type == 'ga'
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: ${{ matrix.jdk.distribution || 'temurin' }}
java-version: ${{ matrix.jdk.version }}
check-latest: true
- name: 'Prepare JDK${{ matrix.jdk.version }} env var'
shell: bash
run: echo "JDK${{ matrix.jdk.version }}=$JAVA_HOME" >> $GITHUB_ENV
- name: Build
uses: ./.github/actions/run-gradle
with:
encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
# Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318
arguments: >-
-Ptesting.enableJaCoCo=false
-PjavaToolchain.version=${{ matrix.jdk.version }}
-Dscan.tag.JDK_${{ matrix.jdk.version }}
build
--no-configuration-cache
broken-by-issue: ${{ matrix.jdk.broken-by-issue }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ always() }}
with:
name: Open Test Reports (${{ github.job }} ${{ matrix.jdk.version }} (${{ matrix.jdk.release || matrix.jdk.type }}))
path: '**/build/reports/open-test-report.html'
openj9:
strategy:
fail-fast: false
matrix:
jdk: [ 25 ]
name: "OpenJ9 ${{ matrix.jdk }}"
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Set up Test JDK
uses: ./.github/actions/setup-test-jdk
with:
distribution: semeru
- name: 'Set up JDK ${{ matrix.jdk }}'
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: semeru
java-version: ${{ matrix.jdk }}
check-latest: true
- name: 'Prepare JDK${{ matrix.jdk }} env var'
shell: bash
run: echo "JDK${{ matrix.jdk }}=$JAVA_HOME" >> $GITHUB_ENV
- name: Build
uses: ./.github/actions/run-gradle
with:
encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
# Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318
arguments: >-
-Ptesting.enableJaCoCo=false
-PjavaToolchain.version=${{ matrix.jdk }}
-PjavaToolchain.implementation=j9
-Dscan.tag.JDK_${{ matrix.jdk }}
-Dscan.tag.OpenJ9
build
--no-configuration-cache
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ always() }}
with:
name: Open Test Reports (${{ github.job }})
path: '**/build/reports/open-test-report.html'
================================================
FILE: .github/workflows/deploy-docs.yml
================================================
name: Deploy Documentation
on: [ workflow_call, workflow_dispatch ]
permissions: {}
jobs:
deploy_documentation:
name: Deploy Documentation
runs-on: ubuntu-slim
permissions:
actions: write
steps:
- name: Trigger documentation site deployment
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
max_attempts: 10
retry_wait_seconds: 5
timeout_seconds: 20
command: gh workflow run deploy-docs.yml --repo junit-team/junit-framework --ref docs-site
================================================
FILE: .github/workflows/gradle-dependency-submission.yml
================================================
name: Gradle Dependency Submission
on:
push:
branches:
- main
permissions: {}
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
jobs:
dependency-submission:
if: github.repository == 'junit-team/junit-framework'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: 25
check-latest: true
- name: Generate and submit dependency graph
id: dependency-submission
uses: gradle/actions/dependency-submission@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
continue-on-error: true
- name: Generate and submit dependency graph (retry)
if: steps.dependency-submission.outcome == 'failure'
uses: gradle/actions/dependency-submission@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
================================================
FILE: .github/workflows/label-opened-issues.yml
================================================
name: Add label to opened issues
on:
issues:
types:
- opened
permissions: {}
jobs:
label_issues:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const issue = await github.rest.issues.get({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const originalLabels = issue.data.labels.map(l => l.name);
const statusLabels = originalLabels.filter(l => l.startsWith("status: "));
if (statusLabels.length === 0 && !originalLabels.includes("up-for-grabs")) {
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ["status: new"]
})
}
================================================
FILE: .github/workflows/label-pull-request.yml
================================================
name: Copy labels from linked issues to PR
on:
pull_request_target:
types: [opened, reopened] # zizmor: ignore[dangerous-triggers]
permissions: {}
jobs:
copy_labels:
name: Copy labels
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const query = `
query($owner: String!, $repo: String!, $pr: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
closingIssuesReferences(first: 10) {
nodes {
labels(first: 100) {
nodes {
name
}
}
}
}
}
}
}
`;
const {repository} = await github.graphql(query, {
owner: context.repo.owner,
repo: context.repo.repo,
pr: context.issue.number
});
let labels = Array.from(new Set(repository.pullRequest.closingIssuesReferences.nodes
.flatMap((node) => node.labels.nodes.map((label) => label.name))))
.filter((label) => !label.startsWith("status:") && label !== "up-for-grabs");
if (labels.length > 0) {
console.log(`Adding labels to PR: ${labels}`);
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: labels
});
}
================================================
FILE: .github/workflows/lock-resolved-issues.yml
================================================
name: Lock resolved issues
on:
schedule:
- cron: "39 1 * * 0"
workflow_dispatch:
permissions: {}
concurrency:
group: lock-resolved-issues
jobs:
action:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
discussions: write
steps:
- uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v6.0.0
with:
process-only: issues
log-output: true
================================================
FILE: .github/workflows/main.yml
================================================
name: CI
on:
push:
branches:
- main
- 'releases/**'
tags-ignore:
- '**'
pull_request:
branches:
- '**'
concurrency:
# Cancels in-progress runs only for pull requests
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions: {}
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
jobs:
Linux:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Install GraalVM
uses: graalvm/setup-graalvm@bef4b0e916c7dd079bf60fb95d49139f67e32c5f # v1.5.3
with:
distribution: graalvm
version: latest
java-version: 21
- name: Build
uses: ./.github/actions/main-build
with:
encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
# Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318
arguments: >-
:platform-tooling-support-tests:test
build
jacocoRootReport
--no-configuration-cache
- name: Upload to Codecov.io
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
Windows:
runs-on: windows-latest
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Build
uses: ./.github/actions/main-build
with:
encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
macOS:
runs-on: macos-latest
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Build
uses: ./.github/actions/main-build
with:
encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
publish_artifacts:
name: Publish Snapshot Artifacts
needs: macOS
runs-on: ubuntu-latest
permissions:
attestations: write # required for build provenance attestation
id-token: write # required for build provenance attestation
if: github.event_name == 'push' && github.repository == 'junit-team/junit-framework' && (startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main')
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Publish
uses: ./.github/actions/run-gradle
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
with:
encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
arguments: >-
publishAggregationToCentralSnapshots
prepareGitHubAttestation
-x check
- name: Generate build provenance attestations
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
with:
subject-path: documentation/build/attestation/*.jar
build_documentation:
name: Build Documentation
needs: macOS
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Install Graphviz
run: |
sudo apt-get update
sudo apt-get install graphviz
- name: Install Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: documentation/.tool-versions
- name: Build Documentation
uses: ./.github/actions/run-gradle
with:
encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
arguments: >-
antora
-Pantora.downloadNode=false
-Dscan.tag.Documentation
deploy_documentation:
name: Deploy Documentation
needs: build_documentation
if: github.event_name == 'push' && github.repository == 'junit-team/junit-framework' && github.ref == 'refs/heads/main'
uses: ./.github/workflows/deploy-docs.yml
permissions:
actions: write
================================================
FILE: .github/workflows/ossf-scorecard.yml
================================================
name: OpenSSF Scorecard supply-chain security analysis
on:
branch_protection_rule:
schedule:
- cron: '31 20 * * 6'
push:
branches: [ "main" ]
permissions: {}
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
steps:
- name: "Checkout code"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecard on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
sarif_file: results.sarif
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
workflow_dispatch:
inputs:
releaseVersion:
description: Version to be released (e.g. "5.12.0-M1")
required: true
deploymentId:
description: ID of the Maven Central Publish Portal deployment
required: true
dryRun:
type: boolean
description: Enable dry-run mode
required: false
default: false
permissions: {}
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
STAGING_REPO_URL: https://central.sonatype.com/api/v1/publisher/deployment/${{ inputs.deploymentId }}/download
RELEASE_TAG: r${{ inputs.releaseVersion }}
RELEASE_VERSION: ${{ inputs.releaseVersion }}
jobs:
verify_reproducibility:
name: Verify reproducibility
runs-on: ubuntu-latest
permissions:
attestations: write # required for build provenance attestation
id-token: write # required for build provenance attestation
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
ref: "refs/tags/${{ env.RELEASE_TAG }}"
persist-credentials: false
- name: Prepare Maven Central user token
uses: ./.github/actions/maven-central-user-token
with:
username: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
- name: Download reference JAR from staging repository
id: referenceJar
run: |
curl --silent --fail --location --output /tmp/reference.jar \
--header "Authorization: Bearer $MAVEN_CENTRAL_USER_TOKEN" \
"${STAGING_REPO_URL}/org/junit/jupiter/junit-jupiter-api/${RELEASE_VERSION}/junit-jupiter-api-${RELEASE_VERSION}.jar"
sudo apt-get update && sudo apt-get install --yes jc
unzip -c /tmp/reference.jar META-INF/MANIFEST.MF | jc --jar-manifest | jq '.[0]' > /tmp/manifest.json
echo "createdBy=$(jq --raw-output .Created_By /tmp/manifest.json)" >> "$GITHUB_OUTPUT"
echo "buildTimestamp=$(jq --raw-output .Build_Date /tmp/manifest.json) $(jq --raw-output .Build_Time /tmp/manifest.json)" >> "$GITHUB_OUTPUT"
- name: Verify artifacts
uses: ./.github/actions/run-gradle
with:
encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
arguments: >-
--rerun-tasks
-Pmanifest.buildTimestamp="${{ steps.referenceJar.outputs.buildTimestamp }}"
-Pmanifest.createdBy="${{ steps.referenceJar.outputs.createdBy }}"
:verifyArtifactsInStagingRepositoryAreReproducible
--remote-repo-url=${{ env.STAGING_REPO_URL }}
- name: Generate build provenance attestations
if: ${{ inputs.dryRun == false }}
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
with:
subject-path: build/repo/**/*.jar
verify_consumability:
name: Verify consumability
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
ref: "refs/tags/${{ env.RELEASE_TAG }}"
path: junit-framework
persist-credentials: false
- name: Check out examples repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: ${{ github.repository_owner }}/junit-examples
token: ${{ secrets.JUNIT_BUILDS_GITHUB_TOKEN_EXAMPLES_REPO }}
fetch-depth: 1
path: junit-examples
persist-credentials: false
- name: Set up JDK
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
java-version: 25
distribution: temurin
- uses: sbt/setup-sbt@93e926cbdb4a428e41b4ef754124ec82925ffdc2 # v1.1.23
- name: Update JUnit dependencies in examples
run: java src/Updater.java ${RELEASE_VERSION}
working-directory: junit-examples
- name: Prepare Maven Central user token
uses: ./junit-framework/.github/actions/maven-central-user-token
with:
username: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
- name: Inject staging repository URL
run: java src/StagingRepoInjector.java ${STAGING_REPO_URL}
working-directory: junit-examples
- name: Build examples
run: java src/Builder.java --exclude=junit-jupiter-starter-bazel,junit-jupiter-starter-sbt,junit-source-launcher
working-directory: junit-examples
env:
MAVEN_ARGS: --settings ${{ github.workspace }}/junit-examples/src/central-staging-maven-settings.xml --activate-profiles central-staging
close_github_milestone:
name: Close GitHub milestone
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Close GitHub milestone
if: ${{ inputs.dryRun == false }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
result-encoding: string
script: |
const closeGithubMilestone = require('./.github/scripts/close-github-milestone.js');
closeGithubMilestone({ github, context });
publish_deployment:
name: Publish to Maven Central
needs: [ verify_reproducibility, verify_consumability, close_github_milestone ]
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
ref: "refs/tags/${{ env.RELEASE_TAG }}"
persist-credentials: false
- name: Release staging repository
if: ${{ inputs.dryRun == false }}
uses: ./.github/actions/run-gradle
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
with:
encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
arguments: nmcpPublishDeployment -PnmcpDeploymentId=${{ inputs.deploymentId }}
publish_documentation:
name: Publish documentation
needs: publish_deployment
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Trigger deployment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run deploy-docs.yml --repo junit-team/junit-framework --ref docs-site
- name: Install Poppler (for pdfinfo)
run: |
sudo apt-get update
sudo apt-get install --yes poppler-utils
- name: Wait for deployment
if: ${{ inputs.dryRun == false }}
timeout-minutes: 30
run: ./.github/scripts/waitForUrl.sh "${URL}"
env:
URL: https://docs.junit.org/${{ inputs.releaseVersion }}/
- name: Verify integrity of PDF version of User Guide
if: inputs.dryRun == false && !contains(inputs.releaseVersion, '-')
run: |
curl --silent --fail --location --output /tmp/junit-user-guide.pdf "${PDF_URL}"
pdfinfo /tmp/junit-user-guide.pdf
env:
PDF_URL: https://docs.junit.org/${{ inputs.releaseVersion }}/_exports/junit-user-guide-${{ inputs.releaseVersion }}.pdf
update_examples:
name: Update examples
needs: publish_deployment
runs-on: ubuntu-latest
steps:
- name: Check out examples repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: ${{ github.repository_owner }}/junit-examples
token: ${{ secrets.JUNIT_BUILDS_GITHUB_TOKEN_EXAMPLES_REPO }}
persist-credentials: true
- name: Set up JDK
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
java-version: 25
distribution: temurin
- uses: sbt/setup-sbt@93e926cbdb4a428e41b4ef754124ec82925ffdc2 # v1.1.23
- name: Update JUnit dependencies in examples
run: java src/Updater.java ${RELEASE_VERSION}
- name: Build examples
if: ${{ inputs.dryRun == false }}
run: java src/Builder.java
- name: Create release branch
run: |
git config user.name "JUnit Team"
git config user.email "team@junit.org"
git switch -c "${RELEASE_TAG}"
git status
git commit -a -m "Use ${RELEASE_VERSION}"
- name: Push release branch
if: ${{ inputs.dryRun == false }}
run: |
git push origin "${RELEASE_TAG}"
- name: Update main branch (only for GA releases)
if: ${{ inputs.dryRun == false && !contains(inputs.releaseVersion, '-') }}
run: |
git fetch origin main
git switch main
git merge --ff-only "${RELEASE_TAG}"
git push origin main
create_github_release:
name: Create GitHub release
if: ${{ inputs.dryRun == false }}
needs: [ publish_documentation, update_examples ]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Create GitHub release
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const createGithubRelease = require('./.github/scripts/create-github-release.js');
createGithubRelease({ github, context });
================================================
FILE: .github/workflows/reproducible-build.yml
================================================
name: Reproducible build
on:
push:
branches:
- main
- 'releases/**'
pull_request:
branches:
- '**'
concurrency:
# Cancels in-progress runs only for pull requests
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions: {}
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
jobs:
check_build_reproducibility:
name: 'Check build reproducibility'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Restore Gradle cache and display toolchains
uses: ./.github/actions/run-gradle
with:
encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
# Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318
arguments: >-
--quiet
--no-configuration-cache
- name: Build and compare checksums
shell: bash
run: ./.github/scripts/checkBuildReproducibility.sh
================================================
FILE: .github/workflows/sanitize-closed-issues.yml
================================================
name: Sanitizes assigned labels and milestone on closed issues
on:
issues:
types:
- closed
permissions: {}
jobs:
label_issues:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const issue = await github.rest.issues.get({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const originalLabels = issue.data.labels.map(l => l.name);
const newLabels = originalLabels.filter(l => l !== "status: in progress" && l !== "status: new" && l !== "status: waiting-for-feedback" && l !== "status: waiting-for-interest");
if (newLabels.length !== originalLabels.length) {
await github.rest.issues.update({
issue_number: issue.data.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: newLabels,
});
}
if (issue.data.state_reason === "not_planned" || issue.data.state_reason === "duplicate") {
if (issue.data.milestone) {
await github.rest.issues.update({
issue_number: issue.data.number,
owner: context.repo.owner,
repo: context.repo.repo,
milestone: null,
});
}
const statusLabels = newLabels.filter(l => l.startsWith("status: "));
if (statusLabels.length === 0) {
if (issue.data.state_reason === "not_planned") {
await github.rest.issues.createComment({
issue_number: issue.data.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Please assign a status label to this issue.",
});
await github.rest.issues.update({
issue_number: issue.data.number,
owner: context.repo.owner,
repo: context.repo.repo,
state: "open",
});
} else {
newLabels.push("status: duplicate");
await github.rest.issues.update({
issue_number: issue.data.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: newLabels,
});
}
}
} else {
if (!(newLabels.includes("type: task") || newLabels.includes("type: question")) && !issue.data.milestone) {
let collaborator;
try {
await github.rest.repos.checkCollaborator({
owner: context.repo.owner,
repo: context.repo.repo,
username: context.actor,
});
collaborator = true;
} catch (error) {
collaborator = false;
}
if (collaborator) {
await github.rest.issues.createComment({
issue_number: issue.data.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Please assign a milestone to this issue or label it with `type: task` or `type: question` – or assign a status label and close it as _not planned_.",
});
await github.rest.issues.update({
issue_number: issue.data.number,
owner: context.repo.owner,
repo: context.repo.repo,
state: "open",
});
} else {
const statusLabels = newLabels.filter(l => l.startsWith("status: "));
if (statusLabels.length === 0) {
newLabels.push("status: invalid");
}
await github.rest.issues.update({
issue_number: issue.data.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: newLabels,
state: "closed",
state_reason: "not_planned",
});
}
}
}
================================================
FILE: .github/workflows/zizmor-analysis.yml
================================================
name: GitHub Actions Security Analysis
on:
push:
branches:
- main
- 'releases/**'
paths:
- '.github/**'
pull_request:
paths:
- '.github/**'
permissions: {}
jobs:
zizmor:
name: Run zizmor 🌈
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
================================================
FILE: .github/zizmor.yml
================================================
rules:
cache-poisoning:
ignore:
# reports issues for setup-node which isn't used while releasing
- main.yml
secrets-outside-env:
disable: true
================================================
FILE: .gitignore
================================================
# Gradle
.gradle
.kotlin
build
# Ignore Gradle GUI config
gradle-app.setting
# Eclipse
.classpath
.settings/
.project
/bin/
/*/bin
/gradle/plugins/*/bin
# IntelliJ
*.iml
*.ipr
*.iws
*.uml
**/.idea/*
!/.idea/icon.png
!/.idea/vcs.xml
/out/
/*/out/
# Misc
*.log
*.graphml
coverage.db*
.metadata
/.sdkmanrc
/.tool-versions
checksums*
# snapshot-tests
*.snapshot_actual
*.snapshot_raw
# Antora
/documentation/node_modules/
================================================
FILE: .idea/vcs.xml
================================================
================================================
FILE: .jitpack.yml
================================================
before_install:
- sdk update
- sdk install java 25-open
- sdk use java 25-open
install:
- |
./gradlew \
--show-version \
-Pjitpack.version=$VERSION \
-Ppublishing.group=$GROUP.$ARTIFACT \
-Ppublishing.signArtifacts=false \
javaToolchains \
publishToMavenLocal
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
## Getting Started
We welcome new contributors to the project!
If you're interested, please check for [issues labeled with `up-for-grabs`
that are not yet in progress](https://github.com/junit-team/junit-framework/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3Aup-for-grabs%20-label%3A%22status%3A%20in%20progress%22).
Generally, before you work on an issue, post a comment and ask whether it can be started.
Please wait for the core team to respond and assign the issue to you before making any code
changes.
## JUnit Contributor License Agreement
- You will only Submit Contributions where You have authored 100% of the content.
- You will only Submit Contributions to which You have the necessary rights. This means
that if You are employed You have received the necessary permissions from Your employer
to make the Contributions.
- Whatever content You Contribute will be provided under the Project License(s).
### Project Licenses
- All modules use [Eclipse Public License v2.0](LICENSE.md).
## Commit Messages
As a general rule, the style and formatting of commit messages should follow the guidelines in
[How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/).
In addition, any commit that is related to an existing issue must reference the issue.
For example, if a commit in a pull request addresses issue \#999, it must contain the
following at the bottom of the commit message.
```
Issue: #999
```
## Pull Requests
Our [Definition of Done](https://github.com/junit-team/junit-framework/wiki/Definition-of-Done)
(DoD) offers some guidelines on what we expect from a pull request.
Feel free to open a pull request that does not fulfill all criteria, e.g. to discuss
a certain change before polishing it, but please be aware that we will only merge it
once the DoD is met.
Please add the following lines to your pull request description:
```markdown
---
I hereby agree to the terms of the JUnit Contributor License Agreement.
```
## Coding Conventions
### Naming Conventions
Acronyms are words.
Whenever an acronym is included as part of a type name or method name, keep the first
letter of the acronym uppercase and use lowercase for the rest of the acronym. Otherwise,
it becomes _impossible_ to perform camel-cased searches in IDEs, and it becomes
potentially very difficult for mere humans to read or reason about the element without
reading documentation (if documentation even exists).
Consider for example a use case needing to support an HTTP URL. Calling the method
`getHTTPURL()` is absolutely horrible in terms of usability; whereas, `getHttpUrl()` is
great in terms of usability. The same applies for types `HTTPURLProvider` vs
`HttpUrlProvider`, etc.
Whenever an acronym is included as part of a field name or parameter name:
- If the acronym comes at the start of the field or parameter name, use lowercase for the
entire acronym -- for example, `String url;`.
- Otherwise, keep the first letter of the acronym uppercase and use lowercase for the
rest of the acronym -- for example, `String defaultUrl;`.
### Formatting
#### Code
Code formatting is enforced using the [Spotless](https://github.com/diffplug/spotless)
Gradle plugin. You can use `gradle spotlessApply` to format new code and add missing
license headers to source files. Formatter and import order settings for Eclipse are
available in the repository under
[junit-eclipse-formatter-settings.xml](gradle/config/eclipse/junit-eclipse-formatter-settings.xml)
and [junit-eclipse.importorder](gradle/config/eclipse/junit-eclipse.importorder),
respectively. For IntelliJ IDEA there's a
[plugin](https://plugins.jetbrains.com/plugin/6546) you can use in conjunction with the
Eclipse settings.
It is forbidden to use _wildcard imports_ (e.g., `import static org.junit.jupiter.api.Assertions.*;`)
in Java code.
#### Documentation
Text in `*.adoc` and `*.md` files should be wrapped at 90 characters whenever technically
possible.
In multi-line bullet point entries, subsequent lines should be indented.
### Spelling
Use American English spelling rules when writing documentation as well as for
code -- class names, method names, variable names, etc.
### Javadoc
- Javadoc comments should be wrapped after 80 characters whenever possible.
- This first paragraph must be a single, concise sentence that ends with a period (`.`).
- Place `
` on the same line as the first line of a new paragraph and precede `
` with a blank line.
- Insert a blank line before at-clauses/tags.
- Favor `{@code foo}` over `foo`.
- Favor literals (e.g., `{@literal @}`) over HTML entities.
- New classes and methods should declare a `@since ...` tag.
- Use `@since 5.10` instead of `@since 5.10.0`.
- Do not use `@author` tags. Instead, contributors are listed on the [GitHub](https://github.com/junit-team/junit-framework/graphs/contributors) page.
- Do not use verbs in third-person form in the first sentence of the Javadoc for a method -- for example, use "Discover tests..." instead of "Discovers tests...".
#### Examples
See [`ExtensionContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java) and
[`ParameterContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java) for example Javadoc.
### Constant fields
A constant field is a `static final` field whose value is immutable. If a static final
field has a primitive type or an immutable reference type it is a constant field.
- To minimize accessibility and mutability for all non-private `static final` fields
under `src/main` should be constant fields.
- Constant fields should be named using uppercase words separated by underscores. For
example `DEFAULT_HTTP_URL_PROVIDER`.
Note: `org.junit.platform.commons.logging.Logger` is considered mutable.
### Nullability
This project uses JSpecify's annotation to indicate nullability. In general, the approach
is as follows:
- All packages are annotated with `@NullMarked`
- Types of fields, parameters, return types etc. may be annotated with `@Nullable`
### Tests
#### Naming
- All test classes must end with a `Tests` suffix.
- Example test classes that should not be picked up by the build must end with a `TestCase` suffix.
#### Assertions
- Use `org.junit.jupiter.api.Assertions` for simple assertions.
- Use AssertJ when richer assertions are needed.
- Do not use `org.junit.Assert` or `junit.framework.Assert`.
#### Mocking and Stubbing
- Use either [Mockito](https://github.com/mockito/mockito) or hand-written test doubles.
### Logging
- In general, logging should be used sparingly.
- All logging must be performed via the internal `Logger` façade provided via the JUnit [LoggerFactory](https://github.com/junit-team/junit-framework/blob/main/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java).
- Levels defined in JUnit's [Logger](https://github.com/junit-team/junit-framework/blob/main/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java) façade, which delegates to Java Util Logging (JUL) for the actual logging.
- _error_ (JUL: `SEVERE`, Log4J: `ERROR`): extra information (in addition to an Exception) about errors that will halt execution
- _warn_ (JUL: `WARNING`, Log4J: `WARN`): potential usage or configuration errors that should not halt execution
- _info_ (JUL: `INFO`, Log4J: `INFO`): information the users might want to know but not by default
- _config_ (JUL: `CONFIG`, Log4J: `CONFIG`): information related to configuration of the system (Example: `ServiceLoaderTestEngineRegistry` logs IDs of discovered engines)
- _debug_ (JUL: `FINE`, Log4J: `DEBUG`)
- _trace_ (JUL: `FINER`, Log4J: `TRACE`)
### Deprecation
The JUnit project uses the `@API` annotation from [API Guardian](https://github.com/apiguardian-team/apiguardian).
Publicly available interfaces, classes, and methods have a defined lifecycle
which is described in detail in the [User Guide](https://docs.junit.org/current/api-evolution.html).
That following describes the deprecation process followed for API items.
To deprecate an item:
- Update the `@API.status` to `DEPRECATED`.
- Update `@API.since`. Please note `since` describes the version when the
status was changed and not the introduction of the element.
- Add the `@Deprecated` Java annotation on the item.
- Add the `@deprecated` JavaDoc tag to describe the deprecation, and refer to
an eventual replacement.
- If the item is used in existing code, add `@SuppressWarnings("deprecation")`
to make the build pass.
## Building the Project
Please refer to [the readme](README.md#building-from-source) and [the documentation readme](documentation/README.md) for
the most common build commands.
### Build Cache
Local builds can reuse outputs from previous CI builds via Gradle's [Build Cache](https://docs.gradle.org/current/userguide/build_cache.html).
The default build cache server is located in the US.
If you're in Europe, you can configure the build to use a build cache node located in the EU by adding the following to the `gradle.properties` file in your [Gradle user home](https://docs.gradle.org/current/userguide/directory_layout.html#dir:gradle_user_home):
```properties
junit.develocity.buildCache.server=https://eu-build-cache-ge.junit.org
```
### Build Parameters
The build can be influenced by a number of parameters. For example, measuring
JaCoCo code coverage of Test tasks can be enabled, or Predictive Test Selection
disabled. To see the full list, please run the following task:
`./gradlew :plugins:build-parameters:parameters`
================================================
FILE: KEYS
================================================
This file contains the PGP key that is used to sign releases.
Importing: `pgp < KEYS` or `gpg --import KEYS`
Adding a key:
`pgp -kxa >> KEYS`,
or `(pgpk -ll && pgpk -xa ) >> KEYS`,
or `(gpg --list-sigs && gpg --armor --export ) >> KEYS`
================================
pub rsa4096 2018-04-08 [SC]
FF6E2C001948C5F2F38B0CC385911F425EC61B51
uid [ unknown] Open Source Development
sig 3 85911F425EC61B51 2018-04-08 Open Source Development
sub rsa4096 2018-04-08 [E]
sig 85911F425EC61B51 2018-04-08 Open Source Development
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFrKW9IBEACkqUvM7hU1WqOOeb1gZ7pUsRliHuoUvYIrd+hdp+qhPmJ0NG0W
YhZK5UtJBmqvtHKRkbwYxUuya9zlBmCfQFf0GpFKJ65JSrPSkZADI3aZ4aUkxIUw
nIRoUHucmr10Xftpebr/zaJk5oR8RdaL5FapapmcZmAaHR9CDWB8XtI318u314jq
M5rKatnAZMERoPugOvvuAOz4bfZKwdfCmZKfYUM/TMSrSinXrGExSW6z4RhtqmpC
E5M/7OoVfvDynVJKqNazqgigpmMNhOyzAhQsiKh1K0akyxTZbjeZKsdYfhCXvq0q
k9+KM/cTllQ54MPnFWiObLkHeK0Waw8bI/vAJ4h4x/XM9iGYpkXv7F2/FVsHQdPe
YJcwD/CkD8KHyiPaRKMeApiUtZsdAHU0L4X/lNmcooea/7ipskruUgwcm+RdLhRZ
P949t1e7nqDZfpEHy90NiFxmlRAPSNqBLwefxY/hwBgog2jabDALJVcLCMosFWPj
MQhFlGSIODiVcW8folGIjzkyNZbNMWkwnl2QnWp/h2TAwYQJOMqcv2MG9o5pyzpx
97Iz1ngq1FlM/gJnGnNUydP2tAjT2L2U3MP1uX/EdRChdgPqdolqYhdFfwCr0Fpf
W527bUZpReHCEiQ29ABSnQ711mO+d9+qM6edRyHUoBWz89IHt8sCunuvNwARAQAB
tC1PcGVuIFNvdXJjZSBEZXZlbG9wbWVudCA8bWFpbEBtYXJjcGhpbGlwcC5kZT6J
Ak4EEwEIADgWIQT/biwAGUjF8vOLDMOFkR9CXsYbUQUCWspb0gIbAwULCQgHAgYV
CAkKCwIEFgIDAQIeAQIXgAAKCRCFkR9CXsYbUQyRD/9xm3BqdpWcRCE5UyB6nbwV
8TgzMmbOhpFhhcjzobly/pKAbcofKsjhreENJkfBVUo+zAFx21ToC5tbH20wRtIE
vQVCP6sAIzhYWU1ohafqVFP4+PztNBuYTnS6vGvSwzp0IXLIIoxSxo0IOED9uUS9
DTxh1n9NnDLDe2pfjrXBblQtLSW3W5ISDoUvcoyO7Hk1OByW6MNsSoLvXIUNeVhB
ju9TfYxFACJSWBhUxJfgip9Y2GrNBJaYGLZrTAoW1Lh1H1DfLV3wHDClQ1+H+oyx
IOZULEGYY3MgZTd6Ner2yNAUCB7gVa50NiCZXCS74m+XzMrTEsdWjSMUaOe+dL0I
9MCrgi4ycUHWIfTKx9gGlIOo3hSDMN+8Nj33XPjLT8kcfoFeUX8jTOvC1HFfTuQJ
x2t/dKHizdrS3F6A/JQa7v8GNTrZFnEXkwgRTf3ccLoo3gPwzNJeCm2xNjvne1VH
fvxzwNmq8M05oicEigvEed2VMStMhvT7dSiMAf66rEJHjjaHAoNqbLDEATYrWUP2
I52txHSSxSJohxVP6Ec6dERnqqYi0mVyzBPo7mmFFBisq74w8RvZXyzvXE3BTiDL
we1E/Z/AXbtJye9DickQ/G6RFtVLbUHQfzyRS/65JPtlH8rqJr58YWlylGImVLwE
OsKNQrwLZ0UkfaWV7wqr3rkCDQRaylvSARAAnQG636wliEOLkXN662OZS6Qz2+cF
ltCWboq9oX9FnA1PHnTY2cAtwS214RfWZxkjg6Stau+d1Wb8TsF/SUN3eKRSyrkA
xlX0v552vj3xmmfNsslQX47e6aEWZ0du0M8jw7/f7Qxp0InkBfpQwjSg4ECoH4cA
6dOFJIdxBv8dgS4K90HNuIHa+QYfVSVMjGwOjD9St6Pwkbg1sLedITRo59Bbv0J1
4nE9LdWbCiwNrkDr24jTewdgrDaCpN6msUwcH1E0nYxuKAetHEi2OpgBhaY3RQ6Q
PQB6NywvmD0xRllMqu4hSp70pHFtm8LvJdWOsJ5we3KijHuZzEbBVTTl+2DhNMI0
KMoh+P/OmyNOfWD8DL4NO3pVv+mPDZn82/eZ3XY1/oSQrpyJaCBjRKasVTtfiA/F
gYqTml6qZMjy6iywg84rLezELgcxHHvjhAKd4CfxyuCCgnGT0iRLFZKw44ZmOUqP
DkyvGRddIyHag1K7UaM/2UMn6iPMy7XWcaFiH5Huhz43SiOdsWGuwNk4dDxHdxmz
Sjps0H5dkfCciOFhEc54AFcGEXCWHXuxVqIq/hwqTmVl1RY+PTcQUIOfx36WW1ix
JQf8TpVxUbooK8vr1jOFF6khorDXoZDJNhI2VKomWp8Y38EPGyiUPZNcnmSiezx+
MoQwAbeqjFMKG7UAEQEAAYkCNgQYAQgAIBYhBP9uLAAZSMXy84sMw4WRH0JexhtR
BQJaylvSAhsMAAoJEIWRH0JexhtR0LEP/RvYGlaokoosAYI5vNORAiYEc1Ow2McP
I1ZafHhcVxZhlwF48dAC2bYcasDX/PbEdcD6pwo8ZU8eI8Ht0VpRQxeV/sP01m2Y
EpAuyZ6jI7IQQCGcwQdN4qzQJxMAASl9JlplH2NniXV1/994FOtesT59ePMyexm5
7lzhYXP1PGcdt8dH37r6z3XQu0lHRG/KBn7YhyA3zwJcno324KdBRJiynlc7uqQq
+ZptU9fR1+Nx0uoWZoFMsrQUmY34aAOPJu7jGMTG+VseMH6vDdNhhZs9JOlD/e/V
aF7NyadjOUD4j/ud7c0z2EwqjDKMFTHGbIdawT/7jartT+9yGUO+EmScBMiMuJUT
dCP4YDh3ExRdqefEBff3uE/rAP73ndNYdIVq9U0gY0uSNCD9JPfj4aCN52y9a2pS
7Dg7KB/Z8SH1R9IWP+t0HvVtAILdsLExNFTedJGHRh7uaC7pwRz01iivmtAKYICz
ruqlJie/IdEFFK/sus6fZek29odTrQxx42HGHO5GCNyEdK9jKVAeuZ10vcaNbuBp
iP7sf8/BsiEU4wHE8gjFeUPRiSjnERgXQwfJosLgf/K/SShQn2dCkYZRNF+SWJ6Z
2tQxcW5rpUjtclV/bRVkUX21EYfwA6SMB811mI7AVy8WPXCe8La72ukmaxEGbpJ8
mdzS2PJko7mm
=l0XA
-----END PGP PUBLIC KEY BLOCK-----
================================================
FILE: LICENSE.md
================================================
Eclipse Public License - v 2.0
==============================
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
### 1. Definitions
“Contribution” means:
* **a)** in the case of the initial Contributor, the initial content Distributed under this Agreement, and
* **b)** in the case of each subsequent Contributor:
* **i)** changes to the Program, and
* **ii)** additions to the Program;
where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works.
“Contributor” means any person or entity that Distributes the Program.
“Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.
“Program” means the Contributions Distributed in accordance with this Agreement.
“Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors.
“Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship.
“Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof.
“Distribute” means the acts of **a)** distributing or **b)** making available in any manner that enables the transfer of a copy.
“Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files.
“Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor.
### 2. Grant of Rights
**a)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works.
**b)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder.
**c)** Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.
**d)** Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.
**e)** Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3).
### 3. Requirements
**3.1** If a Contributor Distributes the Program in any form, then:
* **a)** the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and
* **b)** the Contributor may Distribute the Program under a license different than this Agreement, provided that such license:
* **i)** effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;
* **ii)** effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;
* **iii)** does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and
* **iv)** requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3.
**3.2** When the Program is Distributed as Source Code:
* **a)** it must be made available under this Agreement, or if the Program **(i)** is combined with other material in a separate file or files made available under a Secondary License, and **(ii)** the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and
* **b)** a copy of this Agreement must be included with each copy of the Program.
**3.3** Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (“notices”) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices.
### 4. Commercial Distribution
Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: **a)** promptly notify the Commercial Contributor in writing of such claim, and **b)** allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.
For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.
### 5. No Warranty
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations.
### 6. Disclaimer of Liability
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
### 7. General
If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version.
Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement.
#### Exhibit A - Form of Secondary Licenses Notice
> “This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.”
Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses.
If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
You may add additional accurate notices of copyright ownership.
================================================
FILE: NOTICE.md
================================================
Open Source Licenses
====================
This product may include a number of subcomponents with separate
copyright notices and license terms. Your use of the source code for
these subcomponents is subject to the terms and conditions of the
subcomponent's license, as noted in the `LICENSE-[.md]`
files.
================================================
FILE: README.md
================================================
This repository is the home of JUnit Platform, Jupiter, and Vintage.
## Sponsors
[](https://junit.org/sponsoring)
* **Gold Sponsors:** [JetBrains](https://jb.gg/junit-logo), [Netflix](https://www.netflix.com/)
* **Silver Sponsors:** [Micromata](https://www.micromata.de), [Quo Card](https://quo-digital.jp)
* **Bronze Sponsors:** [Premium Minds](https://www.premium-minds.com), [codefortynine](https://codefortynine.com), [Info Support](https://www.infosupport.com), [Code Intelligence](https://www.code-intelligence.com), [Route4Me](https://route4me.com/), [Testiny](https://www.testiny.io/), [TestMu AI](https://www.testmuai.com/?utm_medium=sponsor&utm_source=junit)
## Latest Releases
- General Availability (GA): [JUnit 6.0.3](https://github.com/junit-team/junit-framework/releases/tag/r6.0.3) (February 15, 2026)
- Preview (Milestone/Release Candidate): [JUnit 6.1.0-RC1](https://github.com/junit-team/junit-framework/releases/tag/r6.1.0-RC1) (April 25, 2026)
## Documentation
- [User Guide]
- [Javadoc]
- [Release Notes]
- [Examples]
## Contributing
Contributions to JUnit are both welcomed and appreciated. For specific guidelines
regarding contributions, please see [CONTRIBUTING.md] in the root directory of the
project. Those willing to use milestone or SNAPSHOT releases are encouraged
to file feature requests and bug reports using the project's
[issue tracker](https://github.com/junit-team/junit-framework/issues). Issues marked with an
`up-for-grabs`
label are specifically targeted for community contributions.
## Getting Help
Ask JUnit-related questions on [StackOverflow] or use the Q&A category on [GitHub Discussions].
## Continuous Integration Builds
[](https://github.com/junit-team/junit-framework/actions/workflows/main.yml) [](https://github.com/junit-team/junit-framework/actions/workflows/cross-version.yml)
Official CI build server used to perform quick checks on submitted pull requests and for
build matrices including the latest released OpenJDK and early access builds of the next
OpenJDK.
## Code Coverage
Code coverage using [JaCoCo] for the latest build is available on [Codecov].
A code coverage report can also be generated locally via the [Gradle Wrapper] by
executing `./gradlew clean jacocoRootReport`. The results will be available
in `build/reports/jacoco/jacocoRootReport/html/index.html`.
## Develocity
[](https://ge.junit.org/scans)
JUnit utilizes [Develocity](https://gradle.com/) for [Build Scans](https://scans.gradle.com/),
[Build Cache](https://docs.gradle.org/current/userguide/build_cache.html), and
[Predictive Test Selection](https://docs.gradle.com/enterprise/predictive-test-selection/).
The latest Build Scans are available on [ge.junit.org](https://ge.junit.org/). Currently,
only core team members can publish Build Scans on that server.
You can, however, publish a Build Scan to [scans.gradle.com](https://scans.gradle.com/) by
using the `--scan` parameter explicitly.
The remote Build Cache is enabled by default for everyone so that local builds can reuse
task outputs from previous CI builds.
## Building from Source
You need [JDK 25] to build JUnit. [Gradle toolchains] are used to detect and
potentially download additional JDKs for compilation and test execution.
All modules can be _built_ and _tested_ with the [Gradle Wrapper] using the following command:
`./gradlew build`
All modules can be _installed_ in a local Maven repository for consumption in other local
projects via the following command:
`./gradlew publishToMavenLocal`
## Dependency Metadata
[](https://central.sonatype.com/search?namespace=org.junit.jupiter)
[](https://central.sonatype.com/search?namespace=org.junit.vintage)
[](https://central.sonatype.com/search?namespace=org.junit.platform)
Consult the [Dependency Metadata] section of the [User Guide] for a list of all artifacts
of the JUnit Platform, JUnit Jupiter, and JUnit Vintage.
[Codecov]: https://codecov.io/gh/junit-team/junit-framework
[CONTRIBUTING.md]: https://github.com/junit-team/junit-framework/blob/HEAD/CONTRIBUTING.md
[Dependency Metadata]: https://docs.junit.org/current/appendix.html#dependency-metadata
[GitHub Discussions]: https://github.com/junit-team/junit-framework/discussions/categories/q-a
[Gradle toolchains]: https://docs.gradle.org/current/userguide/toolchains.html
[Gradle Wrapper]: https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:using_wrapper
[JaCoCo]: https://www.eclemma.org/jacoco/
[Javadoc]: https://api.junit.org
[JDK 25]: https://javaalmanac.io/jdk/25/
[Release Notes]: https://docs.junit.org/current/release-notes.html
[Examples]: https://github.com/junit-team/junit-examples
[StackOverflow]: https://stackoverflow.com/questions/tagged/junit5
[User Guide]: https://docs.junit.org
================================================
FILE: RELEASING.md
================================================
# Releasing
## Pre-release steps
- [ ] Switch or create the release branch for this feature release (e.g. `releases/5.12.x`)
- [ ] Change `version` in `gradle.properties` to the versions about to be released
- [ ] Change release date in Release Notes
- [ ] Change release date in `README.MD`
- [ ] Commit with message "Release ${VERSION}"
- [ ] Execute `./gradlew --no-build-cache --no-configuration-cache clean build publishAggregationToCentralPortal`
- [ ] Tag current commit: `git tag -s -m ${VERSION} r${VERSION}`
- [ ] Change `version` in `gradle.properties` on release branch to new development versions and commit with message "Back to snapshots for further development" or similar
- [ ] Push release branch and tag to GitHub: `git push --set-upstream --follow-tags origin HEAD`
- [ ] Trigger a [release build](https://github.com/junit-team/junit-framework/actions/workflows/release.yml): `gh workflow run --ref r${VERSION} -f releaseVersion=${VERSION} -f deploymentId=${DEPLOYMENT_ID} release.yml`
- Select the release branch
- Enter the version to be released
- Enter the staging repository ID from the output of above Gradle build
## Post-release steps
- [ ] Post about the new release:
- [ ] [Mastodon](https://fosstodon.org/@junit)
- [ ] [Bluesky](https://bsky.app/profile/junit.org)
### Preview releases (milestones and release candidates)
- [ ] Fast-forward merge the release branch to `main` and push to GitHub
- [ ] Create release notes for the next preview or feature release from the template
### Feature releases (x.y.0)
- [ ] Delete `accepted-breaking-changes.csv` (if it exists), change `previousVersion` in `gradle.properties` to `x.y.0` on the release branch, and commit with message "Update API baseline and clear accepted breaking changes"
- [ ] Fast-forward merge the release branch to `main` and push to GitHub
- [ ] Update the [security policy](https://github.com/junit-team/junit-framework/blob/main/SECURITY.md) and commit with message "Update security policy to reflect 5.x release" or similar
- [ ] Create release notes for the next feature release from the template
- [ ] Update [JBang catalog](https://github.com/junit-team/jbang-catalog/blob/main/jbang-catalog.json)
### Patch releases (x.y.z)
- [ ] Cherry-pick the tagged commit from the release branch to `main` and resolve the conflict in `gradle.properties` by choosing the version of the `main` branch
- [ ] Delete `accepted-breaking-changes.csv` (if it exists), change `previousVersion` in `gradle.properties` to `x.y.z` on the release branch, and commit with message "Update API baseline and clear accepted breaking changes"
- [ ] Include the release notes of the patch release on `main` if not already present
- [ ] Update [JBang catalog](https://github.com/junit-team/jbang-catalog/blob/main/jbang-catalog.json)
================================================
FILE: SECURITY.md
================================================
# Security Policy
[](https://www.bestpractices.dev/projects/9607) [](https://scorecard.dev/viewer/?uri=github.com/junit-team/junit-framework)
## JAR Signing
JUnit JARs released on Maven Central are signed.
You'll find more information about the key here: [KEYS](./KEYS)
## Supported Versions
| Version | Supported |
|---------| ------------------ |
| 6.0.x | :white_check_mark: |
| 5.14.x | :white_check_mark: |
| < 5.14 | :x: |
## Reporting a Vulnerability
To report a security vulnerability, you have two options:
- [Privately report a vulnerability](https://github.com/junit-team/junit-framework/security/advisories/new) on GitHub (see [docs](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) for details)
- Send an email to security@junit.org. You can use the [published OpenPGP key](https://keys.openpgp.org/search?q=security%40junit.org) with fingerprint `0152DA30EABC7ABADCB09D10D9A6B1329D191D25` to encrypt the message body.
================================================
FILE: build.gradle.kts
================================================
import junitbuild.extensions.dependencyProject
plugins {
id("junitbuild.base-conventions")
id("junitbuild.build-metadata")
id("junitbuild.checkstyle-nohttp")
id("junitbuild.jacoco-aggregation-conventions")
id("junitbuild.temp-maven-repo")
}
description = "JUnit"
group = "org.junit"
val license by extra(License(
name = "Eclipse Public License v2.0",
url = uri("https://www.eclipse.org/legal/epl-v20.html"),
headerFile = layout.projectDirectory.file("gradle/config/spotless/eclipse-public-license-2.0.java")
))
val platformProjects by extra(listOf(
projects.junitPlatformCommons,
projects.junitPlatformConsole,
projects.junitPlatformConsoleStandalone,
projects.junitPlatformEngine,
projects.junitPlatformLauncher,
projects.junitPlatformReporting,
projects.junitPlatformSuite,
projects.junitPlatformSuiteApi,
projects.junitPlatformSuiteEngine,
projects.junitPlatformTestkit
).map { dependencyProject(it) })
val jupiterProjects by extra(listOf(
projects.junitJupiter,
projects.junitJupiterApi,
projects.junitJupiterEngine,
projects.junitJupiterMigrationsupport,
projects.junitJupiterParams
).map { dependencyProject(it) })
val vintageProjects by extra(listOf(
dependencyProject(projects.junitVintageEngine)
))
val mavenizedProjects by extra(listOf(dependencyProject(projects.junitStart)) + platformProjects + jupiterProjects + vintageProjects)
val modularProjects by extra(mavenizedProjects - setOf(dependencyProject(projects.junitPlatformConsoleStandalone)))
dependencies {
modularProjects.forEach {
jacocoAggregation(it)
}
jacocoAggregation(projects.documentation)
jacocoAggregation(projects.jupiterTests)
jacocoAggregation(projects.platformTests)
}
================================================
FILE: documentation/.tool-versions
================================================
nodejs 24.15.0
================================================
FILE: documentation/README.md
================================================
# JUnit User Guide
This subproject contains the Antora/AsciiDoc sources for the JUnit User Guide.
## Structure
- `modules/ROOT/pages`: AsciiDoc files
- `src/test/java`: Java test source code that can be included in the AsciiDoc files
- `src/test/kotlin`: Kotlin test source code that can be included in the AsciiDoc files
- `src/test/resources`: Classpath resources that can be included in the AsciiDoc files or
used in tests
## Usage
### Generate Antora site
This following Gradle command generates the HTML version of the User Guide as
`build/antora/build/site`.
```
./gradlew antora
```
On Linux operating systems, the `graphviz` package providing `/usr/bin/dot` must be
installed in order to generate the PlantUML images used in the User Guide.
================================================
FILE: documentation/antora-playbook.yml
================================================
antora:
extensions:
- '@antora/collector-extension'
- require: '@antora/lunr-extension'
index_latest_only: true
index_by_heading: true
- require: '@springio/antora-extensions/root-component-extension'
root_component_name: junit
- require: '@springio/antora-extensions/root-attachments-extension'
- require: '@springio/antora-xref-extension'
site:
title: JUnit User Guide
start_page: junit::overview.adoc
content:
sources:
- url: @GIT_REPO_ROOT@
branches: @GIT_BRANCH_NAME@
start_path: documentation
worktrees: true
asciidoc:
extensions:
- '@asciidoctor/tabs'
attributes:
page-pagination: ''
idprefix: '@'
idseparator: '-@'
table-caption: false
table-frame: 'none@'
table-grid: 'rows@'
table-stripes: 'odd@'
figure-caption: false
example-caption: false
listing-caption: false
hide-uri-scheme: '@'
attribute-missing: warn
tabs-sync-option: ''
runtime:
log:
failure_level: warn
ui:
bundle:
url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable
snapshot: true
supplemental_files:
- path: css/vendor/tabs.css
contents: @GIT_REPO_ROOT@/documentation/node_modules/@asciidoctor/tabs/dist/css/tabs.css
- path: js/vendor/tabs.js
contents: @GIT_REPO_ROOT@/documentation/node_modules/@asciidoctor/tabs/dist/js/tabs.js
- path: partials/footer-scripts.hbs
contents: |
{{#if env.SITE_SEARCH_PROVIDER}}
{{> search-scripts}}
{{/if}}
- path: partials/head-styles.hbs
contents: |
================================================
FILE: documentation/antora.yml
================================================
name: junit
version: true
title: JUnit
nav:
- modules/ROOT/nav.adoc
- modules/ROOT/extra-site-nav.adoc
ext:
collector:
run:
command: gradlew --quiet :documentation:generateAntoraResources
local: true
scan:
- dir: ./build/generated-antora-resources
clean: true
- dir: ./build/docs/fixedJavadoc
clean: true
into: modules/ROOT/attachments/api
- dir: ./src/test
into: modules/ROOT/examples
- dir: ./build/generated/asciidoc
clean: true
into: modules/ROOT/partials
- dir: ./build/plantuml
clean: true
into: modules/ROOT/images
assembler:
nav:
- modules/ROOT/nav.adoc
asciidoc:
attributes:
javadoc-root: "xref:attachment$api"
# Snapshot Repository
snapshot-repo: 'https://central.sonatype.com/service/rest/repository/browse/maven-snapshots'
# Base Links
junit-team: 'https://github.com/junit-team'
junit-framework-repo: '{junit-team}/junit-framework'
current-branch: '{junit-framework-repo}/tree/{release-branch}'
# Platform Commons
junit-platform-support-package: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/package-summary.html[org.junit.platform.commons.support]'
AnnotationSupport: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/AnnotationSupport.html[AnnotationSupport]'
ClassSupport: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ClassSupport.html[ClassSupport]'
ConversionSupport: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/conversion/ConversionSupport.html[ConversionSupport]'
ModifierSupport: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ModifierSupport.html[ModifierSupport]'
ReflectionSupport: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ReflectionSupport.html[ReflectionSupport]'
Testable: '{javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/annotation/Testable.html[@Testable]'
# Platform Console Launcher
junit-platform-console: '{javadoc-root}/org.junit.platform.console/org/junit/platform/console/package-summary.html[junit-platform-console]'
ConsoleLauncher: '{javadoc-root}/org.junit.platform.console/org/junit/platform/console/ConsoleLauncher.html[ConsoleLauncher]'
# Platform Engine
junit-platform-engine: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/package-summary.html[junit-platform-engine]'
junit-platform-engine-support-discovery: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/discovery/package-summary.html[org.junit.platform.engine.support.discovery]'
CancellationToken: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/CancellationToken.html[CancellationToken]'
ClasspathResourceSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClasspathResourceSelector.html[ClasspathResourceSelector]'
ClasspathRootSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClasspathRootSelector.html[ClasspathRootSelector]'
ClassSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClassSelector.html[ClassSelector]'
DiscoveryIssue: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/DiscoveryIssue.html[DiscoveryIssue]'
DiscoveryIssueReporter: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/discovery/DiscoveryIssueReporter.html[DiscoveryIssueReporter]'
DirectorySelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DirectorySelector.html[DirectorySelector]'
DiscoverySelectors: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html[DiscoverySelectors]'
DiscoverySelectors_selectClasspathResource: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectClasspathResource(java.lang.String)[selectClasspathResource]'
DiscoverySelectors_selectClasspathRoots: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectClasspathRoots(java.util.Set)[selectClasspathRoots]'
DiscoverySelectors_selectClass: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectClass(java.lang.String)[selectClass]'
DiscoverySelectors_selectDirectory: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectDirectory(java.lang.String)[selectDirectory]'
DiscoverySelectors_selectFile: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectFile(java.lang.String)[selectFile]'
DiscoverySelectors_selectIteration: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectIteration(org.junit.platform.engine.DiscoverySelector,int\...)[selectIteration]'
DiscoverySelectors_selectMethod: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectMethod(java.lang.String)[selectMethod]'
DiscoverySelectors_selectModule: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectModule(java.lang.String)[selectModule]'
DiscoverySelectors_selectNestedClass: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectNestedClass(java.util.List,java.lang.Class)[selectNestedClass]'
DiscoverySelectors_selectNestedMethod: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectNestedMethod(java.util.List,java.lang.Class,java.lang.String)[selectNestedMethod]'
DiscoverySelectors_selectPackage: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectPackage(java.lang.String)[selectPackage]'
DiscoverySelectors_selectUniqueId: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectUniqueId(java.lang.String)[selectUniqueId]'
DiscoverySelectors_selectUri: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectUri(java.lang.String)[selectUri]'
EngineDiscoveryListener: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/EngineDiscoveryListener.html[EngineDiscoveryListener]'
EngineDiscoveryRequest: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/EngineDiscoveryRequest.html[EngineDiscoveryRequest]'
FileSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/FileSelector.html[FileSelector]'
HierarchicalTestEngine: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.html[HierarchicalTestEngine]'
IterationSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/IterationSelector.html[IterationSelector]'
MethodSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/MethodSelector.html[MethodSelector]'
ModuleSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ModuleSelector.html[ModuleSelector]'
NamespacedHierarchicalStore: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.html[NamespacedHierarchicalStore]'
NestedClassSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/NestedClassSelector.html[NestedClassSelector]'
NestedMethodSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/NestedMethodSelector.html[NestedMethodSelector]'
OutputDirectoryCreator: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/OutputDirectoryCreator.html[OutputDirectoryCreator]'
PackageSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/PackageSelector.html[PackageSelector]'
ParallelExecutionConfigurationStrategy: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.html[ParallelExecutionConfigurationStrategy]'
UniqueIdSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/UniqueIdSelector.html[UniqueIdSelector]'
UriSelector: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/UriSelector.html[UriSelector]'
TestEngine: '{javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/TestEngine.html[TestEngine]'
# Platform Launcher API
junit-platform-launcher: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/package-summary.html[junit-platform-launcher]'
DiscoveryIssueException: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/DiscoveryIssueException.html[DiscoveryIssueException]'
Launcher: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/Launcher.html[Launcher]'
LauncherConfig: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherConfig.html[LauncherConfig]'
LauncherDiscoveryListener: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryListener.html[LauncherDiscoveryListener]'
LauncherDiscoveryRequest: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryRequest.html[LauncherDiscoveryRequest]'
LauncherDiscoveryRequestBuilder: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.html[LauncherDiscoveryRequestBuilder]'
LauncherExecutionRequest: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherExecutionRequest.html[LauncherExecutionRequest]'
LauncherExecutionRequestBuilder: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherExecutionRequestBuilder.html[LauncherExecutionRequestBuilder]'
LauncherFactory: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherFactory.html[LauncherFactory]'
LauncherInterceptor: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherInterceptor.html[LauncherInterceptor]'
LauncherSession: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSession.html[LauncherSession]'
LauncherSessionListener: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSessionListener.html[LauncherSessionListener]'
LoggingListener: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/LoggingListener.html[LoggingListener]'
PostDiscoveryFilter: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/PostDiscoveryFilter.html[PostDiscoveryFilter]'
SummaryGeneratingListener: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/SummaryGeneratingListener.html[SummaryGeneratingListener]'
TestExecutionListener: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/TestExecutionListener.html[TestExecutionListener]'
TestPlan: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/TestPlan.html[TestPlan]'
UniqueIdTrackingListener: '{javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.html[UniqueIdTrackingListener]'
# Platform Reporting
LegacyXmlReportGeneratingListener: '{javadoc-root}/org.junit.platform.reporting/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.html[LegacyXmlReportGeneratingListener]'
OpenTestReportGeneratingListener: '{javadoc-root}/org.junit.platform.reporting/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.html[OpenTestReportGeneratingListener]'
# Platform Suite
suite-api-package: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/package-summary.html[org.junit.platform.suite.api]'
junit-platform-suite-engine: '{javadoc-root}/org.junit.platform.suite.engine/org/junit/platform/suite/engine/package-summary.html[junit-platform-suite-engine]'
Select: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/Select.html[@Select]'
SelectClasspathResource: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectClasspathResource.html[@SelectClasspathResource]'
SelectClasses: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectClasses.html[@SelectClasses]'
SelectDirectories: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectDirectories.html[@SelectDirectories]'
SelectFile: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectFile.html[@SelectFile]'
SelectMethod: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectMethod.html[@SelectMethod]'
SelectModules: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectModules.html[@SelectModules]'
SelectPackages: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectPackages.html[@SelectPackages]'
SelectUris: '{javadoc-root}/org.junit.platform.suite.api/org/junit/platform/suite/api/SelectUris.html[@SelectUris]'
# Platform Test Kit
testkit-engine-package: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/package-summary.html[org.junit.platform.testkit.engine]'
EngineExecutionResults: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EngineExecutionResults.html[EngineExecutionResults]'
EngineTestKit: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EngineTestKit.html[EngineTestKit]'
Event: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Event.html[Event]'
EventConditions: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EventConditions.html[EventConditions]'
Events: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Events.html[Events]'
EventStatistics: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EventStatistics.html[EventStatistics]'
EventType: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/EventType.html[EventType]'
Executions: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/Executions.html[Executions]'
TerminationInfo: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/TerminationInfo.html[TerminationInfo]'
TestExecutionResultConditions: '{javadoc-root}/org.junit.platform.testkit/org/junit/platform/testkit/engine/TestExecutionResultConditions.html[TestExecutionResultConditions]'
# Jupiter Core API
api-package: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/package-summary.html[org.junit.jupiter.api]'
Assertions: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html[org.junit.jupiter.api.Assertions]'
Assumptions: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assumptions.html[org.junit.jupiter.api.Assumptions]'
AutoClose: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/AutoClose.html[@AutoClose]'
ClassOrderer_ClassName: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.ClassName.html[ClassOrderer.ClassName]'
ClassOrderer_Default: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.Default.html[ClassOrderer.Default]'
ClassOrderer_DisplayName: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.DisplayName.html[ClassOrderer.DisplayName]'
ClassOrderer_OrderAnnotation: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.OrderAnnotation.html[ClassOrderer.OrderAnnotation]'
ClassOrderer_Random: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.Random.html[ClassOrderer.Random]'
ClassOrderer: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.html[ClassOrderer]'
ClassTemplate: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassTemplate.html[@ClassTemplate]'
Disabled: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Disabled.html[@Disabled]'
MethodOrderer_Default: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Default.html[MethodOrderer.Default]'
MethodOrderer_DisplayName: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.DisplayName.html[MethodOrderer.DisplayName]'
MethodOrderer_MethodName: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.MethodName.html[MethodOrderer.MethodName]'
MethodOrderer_OrderAnnotation: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.OrderAnnotation.html[MethodOrderer.OrderAnnotation]'
MethodOrderer_Random: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Random.html[MethodOrderer.Random]'
MethodOrderer: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.html[MethodOrderer]'
Named: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Named.html[Named]'
Order: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Order.html[@Order]'
RepetitionInfo: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/RepetitionInfo.html[RepetitionInfo]'
TestInfo: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestInfo.html[TestInfo]'
TestClassOrder: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestClassOrder.html[@TestClassOrder]'
TestMethodOrder: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestMethodOrder.html[@TestMethodOrder]'
TestReporter: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestReporter.html[TestReporter]'
TestTemplate: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/TestTemplate.html[@TestTemplate]'
# @DefaultLocale and @DefaultTimeZone
DefaultLocale: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/DefaultLocale.html[@DefaultLocale]'
DefaultTimeZone: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/DefaultTimeZone.html[@DefaultTimeZone]'
LocaleProvider: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/LocaleProvider.html[LocaleProvider]'
TimeZoneProvider: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/TimeZoneProvider.html[TimeZoneProvider]'
ReadsDefaultLocale: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/ReadsDefaultLocale.html[@ReadsDefaultLocale]'
ReadsDefaultTimeZone: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/ReadsDefaultTimeZone.html[@ReadsDefaultTimeZone]'
WritesDefaultLocale: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/WritesDefaultLocale.html[@WritesDefaultLocale]'
WritesDefaultTimeZone: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/WritesDefaultTimeZone.html[@WritesDefaultTimeZone]'
# System Property Extensions
ClearSystemProperty: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/ClearSystemProperty.html[@ClearSystemProperty]'
SetSystemProperty: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/SetSystemProperty.html[@SetSystemProperty]'
RestoreSystemProperties: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/RestoreSystemProperties.html[@RestoreSystemProperties]'
ReadsSystemProperty: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/ReadsSystemProperty.html[@ReadsSystemProperty]'
WritesSystemProperty: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/util/WritesSystemProperty.html[@WritesSystemProperty]'
# Jupiter Parallel API
Execution: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Execution.html[@Execution]'
Isolated: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Isolated.html[@Isolated]'
ResourceLock: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLock.html[@ResourceLock]'
ResourceLockTarget: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLockTarget.html[ResourceLockTarget]'
ResourceLocksProvider: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLocksProvider.html[ResourceLocksProvider]'
Resources: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Resources.html[Resources]'
# Jupiter Extension APIs
extension-api-package: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/package-summary.html[org.junit.jupiter.api.extension]'
AfterAllCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterAllCallback.html[AfterAllCallback]'
AfterClassTemplateInvocationCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterClassTemplateInvocationCallback.html[AfterClassTemplateInvocationCallback]'
AfterEachCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterEachCallback.html[AfterEachCallback]'
AfterTestExecutionCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/AfterTestExecutionCallback.html[AfterTestExecutionCallback]'
ParameterContext: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ParameterContext.html[ParameterContext]'
BeforeAllCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeAllCallback.html[BeforeAllCallback]'
BeforeClassTemplateInvocationCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeClassTemplateInvocationCallback.html[BeforeClassTemplateInvocationCallback]'
BeforeEachCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeEachCallback.html[BeforeEachCallback]'
BeforeTestExecutionCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.html[BeforeTestExecutionCallback]'
ClassTemplateInvocationContext: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ClassTemplateInvocationContext.html[ClassTemplateInvocationContext]'
ClassTemplateInvocationContextProvider: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ClassTemplateInvocationContextProvider.html[ClassTemplateInvocationContextProvider]'
ExecutableInvoker: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutableInvoker.html[ExecutableInvoker]'
ExecutionCondition: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutionCondition.html[ExecutionCondition]'
ExtendWith: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtendWith.html[@ExtendWith]'
ExtensionContext: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtensionContext.html[ExtensionContext]'
ExtensionContext_Store: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtensionContext.Store.html[Store]'
ExtensionContext_StoreScope: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtensionContext.StoreScope.html[StoreScope]'
InvocationInterceptor: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/InvocationInterceptor.html[InvocationInterceptor]'
LifecycleMethodExecutionExceptionHandler: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.html[LifecycleMethodExecutionExceptionHandler]'
ParameterResolver: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ParameterResolver.html[ParameterResolver]'
RegisterExtension: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/RegisterExtension.html[@RegisterExtension]'
TestExecutionExceptionHandler: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.html[TestExecutionExceptionHandler]'
TestInstanceFactory: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstanceFactory.html[TestInstanceFactory]'
TestInstancePostProcessor: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePostProcessor.html[TestInstancePostProcessor]'
TestInstancePreConstructCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.html[TestInstancePreConstructCallback]'
TestInstancePreDestroyCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.html[TestInstancePreDestroyCallback]'
TestTemplateInvocationContext: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestTemplateInvocationContext.html[TestTemplateInvocationContext]'
TestTemplateInvocationContextProvider: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.html[TestTemplateInvocationContextProvider]'
TestWatcher: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestWatcher.html[TestWatcher]'
PreInterruptCallback: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/PreInterruptCallback.html[PreInterruptCallback]'
# Jupiter Conditions
DisabledForJreRange: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledForJreRange.html[@DisabledForJreRange]'
DisabledIf: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIf.html[@DisabledIf]'
DisabledIfEnvironmentVariable: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.html[@DisabledIfEnvironmentVariable]'
DisabledIfSystemProperty: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIfSystemProperty.html[@DisabledIfSystemProperty]'
DisabledInNativeImage: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledInNativeImage.html[@DisabledInNativeImage]'
DisabledOnJre: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledOnJre.html[@DisabledOnJre]'
DisabledOnOs: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledOnOs.html[@DisabledOnOs]'
EnabledForJreRange: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledForJreRange.html[@EnabledForJreRange]'
EnabledIf: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIf.html[@EnabledIf]'
EnabledIfEnvironmentVariable: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.html[@EnabledIfEnvironmentVariable]'
EnabledIfSystemProperty: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledIfSystemProperty.html[@EnabledIfSystemProperty]'
EnabledInNativeImage: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledInNativeImage.html[@EnabledInNativeImage]'
EnabledOnJre: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledOnJre.html[@EnabledOnJre]'
EnabledOnOs: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/EnabledOnOs.html[@EnabledOnOs]'
JRE: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/JRE.html[JRE]'
# Jupiter I/O
TempDir: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDir.html[@TempDir]'
TempDirDeletionStrategy: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDirDeletionStrategy.html[TempDirDeletionStrategy]'
TempDirDeletionStrategyIgnoreFailures: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDirDeletionStrategy.IgnoreFailures.html[IgnoreFailures]'
TempDirDeletionStrategyStandard: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDirDeletionStrategy.Standard.html[Standard]'
TempDirFactory: '{javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/io/TempDirFactory.html[TempDirFactory]'
# Jupiter Params
params-provider-package: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/package-summary.html[org.junit.jupiter.params.provider]'
AfterParameterizedClassInvocation: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/AfterParameterizedClassInvocation.html[@AfterParameterizedClassInvocation]'
AnnotationBasedArgumentConverter: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/converter/AnnotationBasedArgumentConverter.html[AnnotationBasedArgumentConverter]'
AnnotationBasedArgumentsProvider: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.html[AnnotationBasedArgumentsProvider]'
AggregateWith: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/AggregateWith.html[@AggregateWith]'
Arguments: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/Arguments.html[Arguments]'
ArgumentsProvider: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/ArgumentsProvider.html[ArgumentsProvider]'
ArgumentsAccessor: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/ArgumentsAccessor.html[ArgumentsAccessor]'
ArgumentsAggregator: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/aggregator/ArgumentsAggregator.html[ArgumentsAggregator]'
ArgumentConverter: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/converter/ArgumentConverter.html[ArgumentConverter]'
BeforeParameterizedClassInvocation: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/BeforeParameterizedClassInvocation.html[@BeforeParameterizedClassInvocation]'
CsvArgumentsProvider: '{junit-framework-repo}/blob/main/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java[CsvArgumentsProvider]'
EmptySource: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/EmptySource.html[@EmptySource]'
FieldSource: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/FieldSource.html[@FieldSource]'
MethodSource: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/MethodSource.html[@MethodSource]'
NullAndEmptySource: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/NullAndEmptySource.html[@NullAndEmptySource]'
NullSource: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/provider/NullSource.html[@NullSource]'
Parameter: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/Parameter.html[@Parameter]'
ParameterizedClass: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedClass.html[@ParameterizedClass]'
ParameterizedTest: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedTest.html[@ParameterizedTest]'
ParameterInfo: '{javadoc-root}/org.junit.jupiter.params/org/junit/jupiter/params/ParameterInfo.html[ParameterInfo]'
ValueArgumentsProvider: '{junit-framework-repo}/blob/main/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java[ValueArgumentsProvider]'
# Jupiter Engine
junit-jupiter-engine: '{javadoc-root}/org.junit.jupiter.engine/org/junit/jupiter/engine/package-summary.html[junit-jupiter-engine]'
# Jupiter Extension Implementations
AutoCloseExtension: '{current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/AutoCloseExtension.java[AutoCloseExtension]'
DisabledCondition: '{current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java[DisabledCondition]'
RepetitionExtension: '{current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java[RepetitionExtension]'
TempDirectory: '{current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java[TempDirectory]'
TestInfoParameterResolver: '{current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java[TestInfoParameterResolver]'
TestReporterParameterResolver: '{current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java[TestReporterParameterResolver]'
TypeBasedParameterResolver: '{current-branch}/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java[TypeBasedParameterResolver]'
# Jupiter Examples
CustomAnnotationParameterResolver: '{current-branch}/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java[CustomAnnotationParameterResolver]'
CustomTypeParameterResolver: '{current-branch}/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java[CustomTypeParameterResolver]'
MapOfListsTypeBasedParameterResolver: '{current-branch}/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java[MapOfListsTypeBasedParameterResolver]'
# Jupiter Migration Support
EnableJUnit4MigrationSupport: '{javadoc-root}/org.junit.jupiter.migrationsupport/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.html[@EnableJUnit4MigrationSupport]'
EnableRuleMigrationSupport: '{javadoc-root}/org.junit.jupiter.migrationsupport/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.html[@EnableRuleMigrationSupport]'
# JUnit Start
JUnit: '{javadoc-root}/org.junit.start/org/junit/start/JUnit.html[JUnit]'
# Vintage
junit-vintage-engine: '{javadoc-root}/org.junit.vintage.engine/org/junit/vintage/engine/package-summary.html[junit-vintage-engine]'
# Examples Repository
junit-examples-repo: '{junit-team}/junit-examples'
junit-jupiter-starter-ant: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-ant[junit-jupiter-starter-ant]'
junit-jupiter-starter-bazel: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-bazel[junit-jupiter-starter-bazel]'
junit-jupiter-starter-gradle-groovy: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-gradle-groovy[junit-jupiter-starter-gradle-groovy]'
junit-jupiter-starter-gradle-kotlin: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-gradle-kotlin[junit-jupiter-starter-gradle-kotlin]'
junit-jupiter-starter-gradle: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-gradle[junit-jupiter-starter-gradle]'
junit-jupiter-starter-maven: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-maven[junit-jupiter-starter-maven]'
junit-jupiter-starter-sbt: '{junit-examples-repo}/tree/{release-branch}/junit-jupiter-starter-sbt[junit-jupiter-starter-sbt]'
# Third-party Links
API: 'https://apiguardian-team.github.io/apiguardian/docs/current/api/[@API]'
API_Guardian: 'https://github.com/apiguardian-team/apiguardian[@API Guardian]'
AssertJ: 'https://assertj.github.io/doc/[AssertJ]'
Checkstyle: 'https://checkstyle.sourceforge.io[Checkstyle]'
DiscussionsQA: 'https://github.com/junit-team/junit-framework/discussions/categories/q-a'
Hamcrest: 'https://hamcrest.org/JavaHamcrest/[Hamcrest]'
Jimfs: 'https://google.github.io/jimfs/[Jimfs]'
Log4j: 'https://logging.apache.org/log4j/2.x/[Log4j]'
Log4j_JDK_Logging_Adapter: 'https://logging.apache.org/log4j/2.x/log4j-jul/index.html[Log4j JDK Logging Adapter]'
Logback: 'https://logback.qos.ch/[Logback]'
LogManager: 'https://docs.oracle.com/en/java/javase/17/docs/api/java.logging/java/util/logging/LogManager.html[LogManager]'
Maven_Central: 'https://central.sonatype.com/[Maven Central]'
MockitoExtension: 'https://github.com/mockito/mockito/blob/release/2.x/subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoExtension.java[MockitoExtension]'
ServiceLoader: '{jdk-javadoc-base-url}/java.base/java/util/ServiceLoader.html[ServiceLoader]'
SpringExtension: 'https://github.com/spring-projects/spring-framework/tree/HEAD/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java[SpringExtension]'
StackOverflow: 'https://stackoverflow.com/questions/tagged/junit5[Stack Overflow]'
Truth: 'https://truth.dev/[Truth]'
OpenTestReporting: 'https://github.com/ota4j-team/open-test-reporting[Open Test Reporting]'
OpenTestReportingCliTool: 'https://github.com/ota4j-team/open-test-reporting#cli-tool-for-validation-and-format-conversion[Open Test Reporting CLI Tool]'
================================================
FILE: documentation/documentation.gradle.kts
================================================
import junitbuild.exec.CaptureJavaExecOutput
import junitbuild.exec.ClasspathSystemPropertyProvider
import junitbuild.exec.GenerateStandaloneConsoleLauncherShadowedArtifactsFile
import junitbuild.exec.RunConsoleLauncher
import junitbuild.extensions.isSnapshot
import junitbuild.extensions.javaModuleName
import junitbuild.javadoc.JavadocValuesOption
import junitbuild.javadoc.ModuleSpecificJavadocFileOption
import junitbuild.javadoc.VersionNumber
import org.gradle.api.tasks.PathSensitivity.RELATIVE
import java.nio.file.Files
import kotlin.io.path.writeLines
plugins {
alias(libs.plugins.plantuml)
id("junitbuild.antora-conventions")
id("junitbuild.build-parameters")
id("junitbuild.kotlin-library-conventions")
id("junitbuild.testing-conventions")
}
val mavenizedProjects: List by rootProject
val modularProjects: List by rootProject
// Because we need to set up Javadoc aggregation
modularProjects.forEach { evaluationDependsOn(it.path) }
javaLibrary {
mainJavaVersion = JavaVersion.VERSION_17
testJavaVersion = JavaVersion.VERSION_17
}
val apiReport = configurations.dependencyScope("apiReport")
val apiReportClasspath = configurations.resolvable("apiReportClasspath") {
extendsFrom(apiReport.get())
}
val standaloneConsoleLauncher = configurations.dependencyScope("standaloneConsoleLauncher")
val standaloneConsoleLauncherClasspath = configurations.resolvable("standaloneConsoleLauncherClasspath") {
extendsFrom(standaloneConsoleLauncher.get())
}
val attestation = configurations.dependencyScope("attestation")
val attestationClasspath = configurations.resolvable("attestationClasspath") {
extendsFrom(attestation.get())
isTransitive = false
}
val allJavadocSinceValues = configurations.dependencyScope("allJavadocSinceValues")
val allJavadocSinceValuesClasspath = configurations.resolvable("allJavadocSinceValuesClasspath") {
extendsFrom(allJavadocSinceValues.get())
attributes {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, named("javadoc-since-values"))
}
}
val tools by sourceSets.creating
val toolsImplementation by configurations.getting
dependencies {
implementation(projects.junitJupiterApi) {
because("Jupiter API is used in src/main/java")
}
compileOnlyApi(libs.jspecify)
// Pull in all "modular projects" to ensure that they are included
// in reports generated by the ApiReportGenerator.
modularProjects.forEach { apiReport(it) }
apiReport(libs.openTestReporting.tooling.spi)
modularProjects.forEach { allJavadocSinceValues(it) }
// Pull in all "mavenized projects" to ensure that they are included
// in the generation of build provenance attestation.
mavenizedProjects.forEach { attestation(it) }
testImplementation(projects.junitJupiterMigrationsupport)
testImplementation(projects.junitPlatformConsole)
testImplementation(projects.junitPlatformSuite)
testImplementation(projects.junitPlatformTestkit)
testImplementation(projects.junitVintageEngine)
testImplementation(kotlin("stdlib"))
testImplementation(libs.kotlinx.coroutines.test)
testRuntimeOnly(kotlin("reflect"))
toolsImplementation(projects.junitPlatformCommons)
toolsImplementation(libs.classgraph)
toolsImplementation(libs.apiguardian)
testImplementation(libs.jimfs) {
because("Jimfs is used in src/test/java")
}
standaloneConsoleLauncher(projects.junitPlatformConsoleStandalone)
}
val buildRevision: String by rootProject.extra
val snapshot = version.isSnapshot()
val releaseBranch = if (snapshot) "HEAD" else "r${version}"
val replaceCurrentDocs = buildParameters.documentation.replaceCurrentDocs
val ota4jDocVersion = libs.versions.opentest4j.map { if (it.isSnapshot()) "snapshot" else it }.get()
val apiGuardianDocVersion = libs.versions.apiguardian.map { if (it.isSnapshot()) "snapshot" else it }.get()
val generatedAsciiDocPath = layout.buildDirectory.dir("generated/asciidoc")
val consoleLauncherOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-options.txt") }
val consoleLauncherDiscoverOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-discover-options.txt") }
val consoleLauncherExecuteOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-execute-options.txt") }
val consoleLauncherEnginesOptionsFile = generatedAsciiDocPath.map { it.file("console-launcher-engines-options.txt") }
val experimentalApisTableFile = generatedAsciiDocPath.map { it.file("experimental-apis-table.adoc") }
val deprecatedApisTableFile = generatedAsciiDocPath.map { it.file("deprecated-apis-table.adoc") }
val standaloneConsoleLauncherShadowedArtifactsFile = generatedAsciiDocPath.map { it.file("console-launcher-standalone-shadowed-artifacts.adoc") }
val jdkJavadocBaseUrl = "https://docs.oracle.com/en/java/javase/${JavaVersion.current().majorVersion}/docs/api"
val elementListsDir = layout.buildDirectory.dir("elementLists")
val externalModulesWithoutModularJavadoc = mapOf(
"org.apiguardian.api" to "https://apiguardian-team.github.io/apiguardian/docs/$apiGuardianDocVersion/api/",
"org.assertj.core" to "https://javadoc.io/doc/org.assertj/assertj-core/${libs.versions.assertj.get()}/",
"org.opentest4j" to "https://ota4j-team.github.io/opentest4j/docs/$ota4jDocVersion/api/",
"org.jspecify" to "https://jspecify.dev/docs/api/",
)
require(externalModulesWithoutModularJavadoc.values.all { it.endsWith("/") }) {
"all base URLs must end with a trailing slash: $externalModulesWithoutModularJavadoc"
}
tasks {
val consoleLauncherTestReportsDir = project.layout.buildDirectory.dir("console-launcher-test-results")
val consoleLauncherTestEventXmlFiles =
files(consoleLauncherTestReportsDir.map { it.asFileTree.matching { include("**/open-test-report.xml") } })
val consoleLauncherTest by registering(RunConsoleLauncher::class) {
args.addAll("execute")
args.addAll("--scan-classpath")
args.addAll("--config=junit.platform.reporting.open.xml.enabled=true")
args.addAll("--config=junit.platform.reporting.open.xml.git.enabled=true")
args.addAll("--config=junit.platform.output.capture.stdout=true")
args.addAll("--config=junit.platform.output.capture.stderr=true")
args.addAll("--config=junit.platform.discovery.issue.severity.critical=warning")
outputs.dir(consoleLauncherTestReportsDir)
argumentProviders.add(CommandLineArgumentProvider {
listOf(
"--reports-dir=${consoleLauncherTestReportsDir.get()}",
)
})
args.addAll("--include-classname", ".*Tests")
args.addAll("--include-classname", ".*Demo")
args.addAll("--exclude-tag", "exclude")
args.addAll("--exclude-tag", "timeout")
doFirst {
consoleLauncherTestReportsDir.get().asFile.deleteRecursively()
}
finalizedBy(generateOpenTestHtmlReport)
}
generateOpenTestHtmlReport {
mustRunAfter(consoleLauncherTest)
inputs.files(consoleLauncherTestEventXmlFiles).withPathSensitivity(RELATIVE).skipWhenEmpty()
argumentProviders += CommandLineArgumentProvider {
consoleLauncherTestEventXmlFiles.files.map { it.absolutePath }.toList()
}
}
register("consoleLauncher") {
hideOutput = false
outputs.upToDateWhen { false }
}
test {
include("**/*Demo.class")
(options as JUnitPlatformOptions).apply {
includeEngines("junit-vintage")
includeTags("timeout")
systemProperty("junit.platform.discovery.issue.severity.critical", "warning")
}
}
check {
dependsOn(consoleLauncherTest)
}
named(tools.compileJavaTaskName) {
options.release.set(25)
}
named("checkstyleTools") {
config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml"))
}
val generateConsoleLauncherOptions by registering(CaptureJavaExecOutput::class) {
classpath.from(standaloneConsoleLauncherClasspath)
mainClass = "org.junit.platform.console.ConsoleLauncher"
args.addAll("--help", "--disable-banner")
outputFile = consoleLauncherOptionsFile
}
val generateConsoleLauncherDiscoverOptions by registering(CaptureJavaExecOutput::class) {
classpath.from(standaloneConsoleLauncherClasspath)
mainClass = "org.junit.platform.console.ConsoleLauncher"
args.addAll("discover", "--help", "--disable-banner")
outputFile = consoleLauncherDiscoverOptionsFile
}
val generateConsoleLauncherExecuteOptions by registering(CaptureJavaExecOutput::class) {
classpath.from(standaloneConsoleLauncherClasspath)
mainClass = "org.junit.platform.console.ConsoleLauncher"
args.addAll("execute", "--help", "--disable-banner")
outputFile = consoleLauncherExecuteOptionsFile
}
val generateConsoleLauncherEnginesOptions by registering(CaptureJavaExecOutput::class) {
classpath.from(standaloneConsoleLauncherClasspath)
mainClass = "org.junit.platform.console.ConsoleLauncher"
args.addAll("engines", "--help", "--disable-banner")
outputFile = consoleLauncherEnginesOptionsFile
}
val generateApiTables by registering(JavaExec::class) {
classpath = tools.runtimeClasspath
mainClass = "org.junit.api.tools.ApiReportGenerator"
systemProperty("api.moduleNames", modularProjects.map { it.javaModuleName }.sorted().joinToString(","))
jvmArgumentProviders += ClasspathSystemPropertyProvider("api.modulePath", apiReportClasspath.get())
argumentProviders += CommandLineArgumentProvider {
listOf(
"DEPRECATED=${deprecatedApisTableFile.get().asFile.absolutePath}",
"EXPERIMENTAL=${experimentalApisTableFile.get().asFile.absolutePath}",
)
}
outputs.cacheIf { true }
outputs.file(deprecatedApisTableFile)
outputs.file(experimentalApisTableFile)
}
val generateStandaloneConsoleLauncherShadowedArtifactsFile by registering(GenerateStandaloneConsoleLauncherShadowedArtifactsFile::class) {
inputJar.fileProvider(standaloneConsoleLauncherClasspath.flatMap { it.elements.map { it.single().asFile } })
outputFile = standaloneConsoleLauncherShadowedArtifactsFile
}
plantUml {
fileFormat = "SVG"
outputs.cacheIf { true }
}
val plantUmlOutputDirectory = plantUml.flatMap { it.outputDirectory }
val generateAsciidocInputs by registering {
dependsOn(
generateConsoleLauncherOptions,
generateConsoleLauncherDiscoverOptions,
generateConsoleLauncherExecuteOptions,
generateConsoleLauncherEnginesOptions,
generateApiTables,
generateStandaloneConsoleLauncherShadowedArtifactsFile,
plantUmlOutputDirectory
)
}
val downloadJavadocElementLists by registering {
outputs.cacheIf { true }
outputs.dir(elementListsDir).withPropertyName("elementListsDir")
inputs.property("externalModulesWithoutModularJavadoc", externalModulesWithoutModularJavadoc)
doFirst {
externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) ->
val resource = resources.text.fromUri("${baseUrl}element-list")
elementListsDir.get().asFile.resolve(moduleName).apply {
mkdir()
resolve("element-list").writeText("module:$moduleName\n${resource.asString()}")
}
}
}
}
val mergeJavadocSinceValues by registering {
inputs.files(allJavadocSinceValuesClasspath).withPathSensitivity(PathSensitivity.NONE)
val outputFile = layout.buildDirectory.file("docs/aggregated-javadoc-since-values.txt")
outputs.file(outputFile)
outputs.cacheIf { true }
doFirst {
val initialVersions = setOf("1.0", "4.12", "5.0")
val noPublicItems = setOf("1.3.1", "5.0.3", "5.4.1", "5.11.3")
val excludes = initialVersions + noPublicItems
val values = allJavadocSinceValuesClasspath.get().files.asSequence()
.flatMap { it.readLines().asSequence() }
.filter { it.isNotBlank() && it !in excludes }
.sortedBy { VersionNumber(it) }
.distinct()
with(outputFile.get().asFile.toPath()) {
Files.createDirectories(parent)
writeLines(values)
}
}
}
val aggregateJavadocs by registering(Javadoc::class) {
dependsOn(modularProjects.map { it.tasks.jar })
dependsOn(downloadJavadocElementLists)
group = "Documentation"
description = "Generates aggregated Javadocs"
title = "JUnit $version API"
val additionalStylesheetFile = "src/javadoc/junit-stylesheet.css"
inputs.file(additionalStylesheetFile)
val overviewFile = "src/javadoc/junit-overview.html"
inputs.file(overviewFile)
options {
memberLevel = JavadocMemberLevel.PROTECTED
header = rootProject.description
encoding = "UTF-8"
locale = "en"
overview = overviewFile
jFlags("-Xmx1g")
this as StandardJavadocDocletOptions
splitIndex(true)
addBooleanOption("Xdoclint:all,-missing", true)
addBooleanOption("Werror", true)
addBooleanOption("html5", true)
addMultilineStringsOption("tag").value = listOf(
"apiNote:a:API Note:",
"implNote:a:Implementation Note:"
)
links(jdkJavadocBaseUrl)
links("https://junit.org/junit4/javadoc/${libs.versions.junit4.get()}/")
externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) ->
linksOffline(baseUrl, elementListsDir.get().asFile.resolve(moduleName).absolutePath)
}
groups = mapOf(
"Jupiter" to listOf("org.junit.jupiter*"),
"Vintage" to listOf("org.junit.vintage*"),
"Platform" to listOf("org.junit.platform*")
)
addStringOption("-add-stylesheet", additionalStylesheetFile)
addBooleanOption("-no-fonts", true)
use(true)
noTimestamp(true)
addStringsOption("-module", ",").value = modularProjects.map { it.javaModuleName }
addOption(ModuleSpecificJavadocFileOption("-module-source-path", modularProjects.associate { project ->
project.javaModuleName to provider {
files(
project.sourceSets.named { it.startsWith("main") }.map {
it.allJava.srcDirs.filter { it.exists() }
}
).asPath
}
}))
addStringOption("-add-modules", "info.picocli,org.opentest4j.reporting.events,de.siegmar.fastcsv")
addOption(ModuleSpecificJavadocFileOption("-add-reads", mapOf(
"org.junit.platform.console" to provider { "info.picocli" },
"org.junit.platform.reporting" to provider { "org.opentest4j.reporting.events" },
"org.junit.jupiter.params" to provider { "de.siegmar.fastcsv" }
)))
val javadocSinceValuesFile: Provider = mergeJavadocSinceValues.map { it.outputs.files.singleFile }
inputs.file(javadocSinceValuesFile)
.withPathSensitivity(PathSensitivity.NONE)
.withPropertyName("javadoc-since-values-file")
addOption(JavadocValuesOption("-since", javadocSinceValuesFile.map { file ->
file.readLines().asSequence().filter { it.isNotBlank() }.toList()
}))
}
source(modularProjects.map { project ->
files(project.sourceSets.named { it.startsWith("main") }.map { it.allJava })
})
classpath = files(modularProjects.map { it.sourceSets.main.get().compileClasspath })
setMaxMemory("1024m")
options.destinationDirectory = layout.buildDirectory.dir("docs/javadoc").get().asFile
}
val fixJavadoc by registering(Copy::class) {
dependsOn(aggregateJavadocs)
group = "Documentation"
description = "Fix links to external API specs in the locally aggregated Javadoc HTML files"
val inputDir = aggregateJavadocs.map { it.destinationDir!! }
inputs.property("externalModulesWithoutModularJavadoc", externalModulesWithoutModularJavadoc)
from(inputDir.map { File(it, "element-list") }) {
// For compatibility with pre JDK 10 versions of the Javadoc tool
rename { "package-list" }
}
from(inputDir) {
filesMatching("**/*.html") {
val favicon =
"""
""".trimIndent()
val version = project.version.toString().replace("-SNAPSHOT", "")
val targetUrl = if (buildParameters.ci)
"https://docs.junit.org/$version"
else
project.antora.siteDir.get().asFile.toURI().resolve(version).toString()
filter { line ->
var result = if (line.startsWith("")) line.replace("", "$favicon") else line
externalModulesWithoutModularJavadoc.forEach { (moduleName, baseUrl) ->
result = result.replace("${baseUrl}$moduleName/", baseUrl)
}
result = result.replace("https://docs.junit.org/current", targetUrl)
return@filter result
}
}
filesMatching("**/stylesheet.css") {
// Remove invalid import of `dejavu.css` due to `javadoc --no-fonts`
filter { line -> if (line.startsWith("@import url('fonts/")) null else line }
}
}
into(layout.buildDirectory.dir("docs/fixedJavadoc"))
}
val prepareGitHubAttestation by registering(Sync::class) {
from(attestationClasspath)
into(layout.buildDirectory.dir("attestation"))
rename("(.*)-SNAPSHOT.jar", "$1-SNAPSHOT+${buildRevision.substring(0, 7)}.jar")
}
generateAntoraYml {
asciidocAttributes.putAll(provider {
mapOf(
"version" to project.version,
"junit4-version" to libs.versions.junit4.get(),
"apiguardian-version" to libs.versions.apiguardian.get(),
"ota4j-version" to libs.versions.opentest4j.get(),
"surefire-version" to libs.versions.surefire.get(),
"release-branch" to releaseBranch,
"jdk-javadoc-base-url" to jdkJavadocBaseUrl
)
})
}
generateAntoraResources {
dependsOn(generateAsciidocInputs, fixJavadoc)
}
}
================================================
FILE: documentation/modules/ROOT/extra-site-nav.adoc
================================================
* {javadoc-root}/index.html[Javadoc]
================================================
FILE: documentation/modules/ROOT/images/_source/extensions_BrokenLifecycleMethodConfigDemo.txt
================================================
@Starter(Platform)
Result = JupiterEngine.execute(BrokenLifecycleMethodConfigDemo) {
Extension1.beforeEach()
Extension2.beforeEach()
// @BeforeEach
BrokenLifecycleMethodConfigDemo.insertTestDataIntoDatabase()
// @BeforeEach
BrokenLifecycleMethodConfigDemo.connectToDatabase()
// @Test
BrokenLifecycleMethodConfigDemo.testDatabaseFunctionality()
// @AfterEach
BrokenLifecycleMethodConfigDemo.disconnectFromDatabase()
// @AfterEach
BrokenLifecycleMethodConfigDemo.deleteTestDataFromDatabase()
Extension2.afterEach()
Extension1.afterEach()
}
================================================
FILE: documentation/modules/ROOT/images/_source/extensions_DatabaseTestsDemo.txt
================================================
@Starter(Platform)
Result = JupiterEngine.execute(DatabaseTestsDemo) {
// @BeforeAll (static invocation)
AbstractDatabaseTests.createDatabase()
// @BeforeAll (static invocation)
DatabaseTestsDemo.beforeAll()
Extension1.beforeEach()
Extension2.beforeEach()
// @BeforeEach inherited from AbstractDatabaseTests
DatabaseTestsDemo.connectToDatabase()
// @BeforeEach
DatabaseTestsDemo.insertTestDataIntoDatabase()
// @Test
DatabaseTestsDemo.testDatabaseFunctionality()
// @AfterEach
DatabaseTestsDemo.deleteTestDataFromDatabase()
// @AfterEach inherited from AbstractDatabaseTests
DatabaseTestsDemo.disconnectFromDatabase()
Extension2.afterEach()
Extension1.afterEach()
// @AfterAll (static invocation)
DatabaseTestsDemo.afterAll()
// @AfterAll (static invocation)
AbstractDatabaseTests.destroyDatabase()
}
================================================
FILE: documentation/modules/ROOT/nav.adoc
================================================
* xref:overview.adoc[]
* xref:writing-tests/intro.adoc[]
** xref:writing-tests/annotations.adoc[]
** xref:writing-tests/definitions.adoc[]
** xref:writing-tests/test-classes-and-methods.adoc[]
** xref:writing-tests/display-names.adoc[]
** xref:writing-tests/assertions.adoc[]
** xref:writing-tests/assumptions.adoc[]
** xref:writing-tests/exception-handling.adoc[]
** xref:writing-tests/disabling-tests.adoc[]
** xref:writing-tests/conditional-test-execution.adoc[]
** xref:writing-tests/tagging-and-filtering.adoc[]
** xref:writing-tests/test-execution-order.adoc[]
** xref:writing-tests/test-instance-lifecycle.adoc[]
** xref:writing-tests/nested-tests.adoc[]
** xref:writing-tests/dependency-injection-for-constructors-and-methods.adoc[]
** xref:writing-tests/test-interfaces-and-default-methods.adoc[]
** xref:writing-tests/repeated-tests.adoc[]
** xref:writing-tests/parameterized-classes-and-tests.adoc[]
** xref:writing-tests/class-templates.adoc[]
** xref:writing-tests/test-templates.adoc[]
** xref:writing-tests/dynamic-tests.adoc[]
** xref:writing-tests/timeouts.adoc[]
** xref:writing-tests/parallel-execution.adoc[]
** xref:writing-tests/built-in-extensions.adoc[]
* xref:migrating-from-junit4.adoc[]
* xref:running-tests/intro.adoc[]
** xref:running-tests/ide-support.adoc[]
** xref:running-tests/build-support.adoc[]
** xref:running-tests/console-launcher.adoc[]
** xref:running-tests/source-launcher.adoc[]
** xref:running-tests/discovery-selectors.adoc[]
** xref:running-tests/configuration-parameters.adoc[]
** xref:running-tests/tags.adoc[]
** xref:running-tests/capturing-standard-output-error.adoc[]
** xref:running-tests/using-listeners-and-interceptors.adoc[]
** xref:running-tests/stack-trace-pruning.adoc[]
** xref:running-tests/discovery-issues.adoc[]
* xref:extensions/overview.adoc[]
** xref:extensions/registering-extensions.adoc[]
** xref:extensions/conditional-test-execution.adoc[]
** xref:extensions/test-instance-pre-construct-callback.adoc[]
** xref:extensions/test-instance-factories.adoc[]
** xref:extensions/test-instance-post-processing.adoc[]
** xref:extensions/test-instance-pre-destroy-callback.adoc[]
** xref:extensions/parameter-resolution.adoc[]
** xref:extensions/test-result-processing.adoc[]
** xref:extensions/test-lifecycle-callbacks.adoc[]
** xref:extensions/exception-handling.adoc[]
** xref:extensions/pre-interrupt-callback.adoc[]
** xref:extensions/intercepting-invocations.adoc[]
** xref:extensions/providing-invocation-contexts-for-class-templates.adoc[]
** xref:extensions/providing-invocation-contexts-for-test-templates.adoc[]
** xref:extensions/keeping-state-in-extensions.adoc[]
** xref:extensions/supported-utilities-in-extensions.adoc[]
** xref:extensions/relative-execution-order-of-user-code-and-extensions.adoc[]
* Advanced Topics
** xref:advanced-topics/junit-platform-reporting.adoc[]
** xref:advanced-topics/junit-platform-suite-engine.adoc[]
** xref:advanced-topics/testkit.adoc[]
** xref:advanced-topics/launcher-api.adoc[]
** xref:advanced-topics/engines.adoc[]
* xref:api-evolution.adoc[]
* xref:release-notes.adoc[]
* xref:appendix.adoc[]
================================================
FILE: documentation/modules/ROOT/pages/advanced-topics/engines.adoc
================================================
= Test Engines
A `TestEngine` facilitates _discovery_ and _execution_ of tests for a particular
programming model.
For example, JUnit provides a `TestEngine` that discovers and executes tests written using
the JUnit Jupiter programming model (see xref:writing-tests/intro.adoc[] and xref:extensions/overview.adoc[]).
[[junit]]
== JUnit Test Engines
JUnit provides three `TestEngine` implementations.
* `{junit-jupiter-engine}`: The core of JUnit Jupiter.
* `{junit-vintage-engine}`: A thin layer on top of JUnit 4 to allow running _vintage_
tests (based on JUnit 3.8 and JUnit 4) with the JUnit Platform launcher infrastructure.
* `{junit-platform-suite-engine}`: Executes declarative suites of tests with the JUnit
Platform launcher infrastructure.
[[custom]]
== Custom Test Engines
You can contribute your own custom `{TestEngine}` by implementing the interfaces in the
{junit-platform-engine} module and _registering_ your engine.
Every `TestEngine` must provide its own _unique ID_, _discover_ tests from an
`EngineDiscoveryRequest`, and _execute_ those tests according to an `ExecutionRequest`.
[WARNING]
.The `junit-` unique ID prefix is reserved for TestEngines from the JUnit Team
====
The JUnit Platform `Launcher` enforces that only `TestEngine` implementations published
by the JUnit Team may use the `junit-` prefix for their `TestEngine` IDs.
* If any third-party `TestEngine` claims to be `junit-jupiter` or `junit-vintage`, an
exception will be thrown, immediately halting execution of the JUnit Platform.
* If any third-party `TestEngine` uses the `junit-` prefix for its ID, a warning message
will be logged. Later releases of the JUnit Platform will throw an exception for such
violations.
====
In order to facilitate test discovery within IDEs and tools prior to launching the JUnit
Platform, `TestEngine` implementations are encouraged to make use of the `@Testable`
annotation. For example, the `@Test` and `@TestFactory` annotations in JUnit Jupiter are
meta-annotated with `@Testable`. Consult the Javadoc for `{Testable}` for further details.
If your custom `TestEngine` needs to be configured, consider allowing users to supply
configuration via xref:running-tests/configuration-parameters.adoc[configuration parameters]. Please note,
however, that you are strongly encouraged to use a unique prefix for all configuration
parameters supported by your test engine. Doing so will ensure that there are no conflicts
between the names of your configuration parameters and those from other test engines. In
addition, since configuration parameters may be supplied as JVM system properties, it is
wise to avoid conflicts with the names of other system properties. For example, JUnit
Jupiter uses `junit.jupiter.` as a prefix of all of its supported configuration
parameters. Furthermore, as with the warning above regarding the `junit-` prefix for
`TestEngine` IDs, you should not use `junit.` as a prefix for the names of your own
configuration parameters.
Although there is currently no official guide on how to implement a custom `TestEngine`,
you can consult the implementation of <> or the implementation of
third-party test engines listed in the
https://github.com/junit-team/junit-framework/wiki/Third-party-Extensions#junit-platform-test-engines[JUnit wiki].
You will also find various tutorials and blogs on the Internet that demonstrate how to
write a custom `TestEngine`.
NOTE: `{HierarchicalTestEngine}` is a convenient abstract base implementation of the
`TestEngine` SPI (used by the `{junit-jupiter-engine}`) that only requires implementors to
provide the logic for test discovery. It implements execution of `TestDescriptors` that
implement the `Node` interface, including support for parallel execution.
[[registration]]
== Registering a TestEngine
`TestEngine` registration is supported via Java's `{ServiceLoader}` mechanism.
For example, the `junit-jupiter-engine` module registers its
`org.junit.jupiter.engine.JupiterTestEngine` in a file named
`org.junit.platform.engine.TestEngine` within the `/META-INF/services` folder in the
`junit-jupiter-engine` JAR.
[[requirements]]
== Requirements
NOTE: The words "must", "must not", "required", "shall", "shall not", "should", "should
not", "recommended", "may", and "optional" in this section are to be interpreted as
described in https://www.ietf.org/rfc/rfc2119.txt[RFC 2119.]
[[requirements-mandatory]]
=== Mandatory requirements
For interoperability with build tools and IDEs, `TestEngine` implementations must adhere
to the following requirements:
* The `TestDescriptor` returned from `TestEngine.discover()` _must_ be the root of a tree
of `TestDescriptor` instances. This implies that there _must not_ be any cycles between
a node and its descendants.
* The hierarchy of test descriptors returned from `TestEngine.discover()` _must_ be
mutable, but the test descriptors _must_ otherwise be immutable after discovery.
* A `TestEngine` _must_ be able to discover `UniqueIdSelectors` for any unique ID that it
previously generated and returned from `TestEngine.discover()`. This enables selecting a
subset of tests to execute or rerun.
* The `executionSkipped`, `executionStarted`, and `executionFinished` methods of the
`EngineExecutionListener` passed to `TestEngine.execute()` _must_ be called for every
`TestDescriptor` node in the tree returned from `TestEngine.discover()` at most
once. Parent nodes _must_ be reported as started before their children and as finished
after their children. If a node is reported as skipped, there _must not_ be any events
reported for its descendants.
[[requirements-enhanced-compatibility]]
=== Enhanced compatibility
Adhering to the following requirements is optional but recommended for enhanced
compatibility with build tools and IDEs:
* Unless to indicate an empty discovery result, the `TestDescriptor` returned from
`TestEngine.discover()` _should_ have children rather than being completely dynamic.
This allows tools to display the structure of the tests and to select a subset of tests
to execute.
* When resolving `UniqueIdSelectors`, a `TestEngine` _should_ only return `TestDescriptor`
instances with matching unique IDs including their ancestors but _may_ return additional
siblings or other nodes that are required for the execution of the selected tests.
* `TestEngines` _should_ support xref:running-tests/tags.adoc[tagging] tests and containers so
that tag filters can be applied when discovering tests.
* [[requirements-cancellation]] A `TestEngine` _should_ cancel its execution
when the `{CancellationToken}` it is passed as part of the `ExecutionRequest` indicates
that cancellation has been requested. In this case, it _should_ report any remaining
`TestDescriptors` as skipped but not report any events for their descendants. It _may_
report already started `TestDescriptors` as aborted in case they have not been executed
completely. If a `TestEngine` supports cancellation, it should clean up any resources
that it has created just like if execution had finished regularly.
[[discovery-issues]]
== Reporting Discovery Issues
Test engines should report xref:running-tests/discovery-issues.adoc[discovery issues] if they
encounter any problems or potential misconfigurations during test discovery. This is
especially important if the issue could lead to tests not being executed at all or only
partially.
In order to report a `{DiscoveryIssue}`, a test engine should call the
`issueEncountered()` method on the `{EngineDiscoveryListener}` available via the
`{EngineDiscoveryRequest}` passed to its `discover()` method. Rather than passing the
listener around, the `{DiscoveryIssueReporter}` interface should be used. It also provides
a way to create a `Condition` that reports a discovery issue if its check fails and may
be used as a `Predicate` or `Consumer`. Please refer to the implementations of the
<> for examples.
Moreover, xref:advanced-topics/testkit.adoc#engine-discovery[Engine Test Kit] provides a way to write tests for
reported discovery issues.
================================================
FILE: documentation/modules/ROOT/pages/advanced-topics/junit-platform-reporting.adoc
================================================
= JUnit Platform Reporting
The `junit-platform-reporting` artifact contains `{TestExecutionListener}` implementations
that generate XML test reports in two flavors:
<> and
<>.
NOTE: The module also contains other `TestExecutionListener` implementations that can be
used to build custom reporting. See xref:running-tests/using-listeners-and-interceptors.adoc[] for details.
[[output-directory]]
== Output Directory
The JUnit Platform provides an `{OutputDirectoryCreator}` via `{EngineDiscoveryRequest}`
and `{TestPlan}` to registered xref:advanced-topics/engines.adoc[test engines] and
xref:running-tests/using-listeners-and-interceptors.adoc[listeners], respectively. Its root directory can be configured
via the following xref:running-tests/configuration-parameters.adoc[configuration parameter]:
`junit.platform.reporting.output.dir=`::
Configure the output directory for reporting. By default, `build` is used if a Gradle
build script is found, and `target` if a Maven POM is found; otherwise, the current
working directory is used.
To create a unique output directory per test run, you can use the `\{uniqueNumber}`
placeholder in the path. For example, `reports/junit-\{uniqueNumber}` will create
directories like `reports/junit-8803697269315188212`. This can be useful when using
Gradle's or Maven's parallel execution capabilities which create multiple JVM forks
that run concurrently.
[[open-test-reporting]]
== Open Test Reporting
`{OpenTestReportGeneratingListener}` writes an XML report for the entire execution in the
event-based format specified by {OpenTestReporting} which supports all features of the
JUnit Platform such as hierarchical test structures, display names, tags, etc.
The listener is auto-registered and can be configured via the following
xref:running-tests/configuration-parameters.adoc[configuration parameters]:
`junit.platform.reporting.open.xml.enabled=true|false`::
Enable/disable writing the report; defaults to `false`.
`junit.platform.reporting.open.xml.git.enabled=true|false`::
Enable/disable including information about the Git repository (see https://github.com/ota4j-team/open-test-reporting#git[Git extension schema] of open-test-reporting); defaults to `false`.
`junit.platform.reporting.open.xml.socket=`::
Optional port number to redirect events to a socket instead of a file. When specified, the
listener will connect to `127.0.0.1:` and send the XML events to the socket. The socket
connection is automatically closed when the test execution completes.
If enabled, the listener creates an XML report file named `open-test-report.xml` in the
configured <>, unless the
`junit.platform.reporting.open.xml.socket` configuration parameter is set, in which case the
events are sent to the specified socket instead.
If xref:running-tests/capturing-standard-output-error.adoc[output capturing] is enabled, the captured output
written to `System.out` and `System.err` will be included in the report as well.
TIP: The {OpenTestReportingCliTool} can be used to convert from the event-based format to
the hierarchical format which is more human-readable.
=== Gradle
For Gradle, writing Open Test Reporting compatible XML reports can be enabled and
configured via system properties. The following samples configure its output directory to
be the same directory Gradle uses for its own XML reports. A `CommandLineArgumentProvider`
is used to keep the tasks relocatable across different machines which is important when
using Gradle's Build Cache.
[source,groovy,indent=0]
[subs=attributes+]
.Groovy DSL
----
dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-reporting:{version}")
}
tasks.withType(Test).configureEach {
def outputDir = reports.junitXml.outputLocation
jvmArgumentProviders << ({
[
"-Djunit.platform.reporting.open.xml.enabled=true",
"-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}"
]
} as CommandLineArgumentProvider)
}
----
[source,kotlin,indent=0]
[subs=attributes+]
.Kotlin DSL
----
dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-reporting:{version}")
}
tasks.withType().configureEach {
val outputDir = reports.junitXml.outputLocation
jvmArgumentProviders += CommandLineArgumentProvider {
listOf(
"-Djunit.platform.reporting.open.xml.enabled=true",
"-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}"
)
}
}
----
=== Maven
For Maven Surefire/Failsafe, you can enable Open Test Reporting output and configure the
resulting XML files to be written to the same directory Surefire/Failsafe uses for its own
XML reports as follows:
[source,xml,indent=0]
[subs=attributes+]
----
org.junit.platform
junit-platform-reporting
{version}
test
maven-surefire-plugin
{surefire-version}
junit.platform.reporting.open.xml.enabled = true
junit.platform.reporting.output.dir = target/surefire-reports
----
=== Console Launcher
When using the xref:running-tests/console-launcher.adoc[], you can enable Open Test Reporting
output by setting the configuration parameters via `--config`:
[source,console,subs=attributes+]
----
$ java -jar junit-platform-console-standalone-{version}.jar \
--config=junit.platform.reporting.open.xml.enabled=true \
--config=junit.platform.reporting.output.dir=reports
----
Configuration parameters can also be set in a custom properties file supplied as a
classpath resource via the `--config-resource` option:
[source,console,subs=attributes+]
----
$ java -jar junit-platform-console-standalone-{version}.jar \
--config-resource=configuration.properties
----
[[legacy-xml]]
== Legacy XML format
`{LegacyXmlReportGeneratingListener}` generates a separate XML report for each root in the
`{TestPlan}`. Note that the generated XML format is compatible with the de facto standard
for JUnit 4 based test reports that was made popular by the Ant build system.
The `LegacyXmlReportGeneratingListener` is used by the xref:running-tests/console-launcher.adoc[]
as well.
================================================
FILE: documentation/modules/ROOT/pages/advanced-topics/junit-platform-suite-engine.adoc
================================================
= JUnit Platform Suite Engine
The Suite Engine supports the declarative selection and execution of tests from _any_ test
engine on the JUnit Platform using the xref:advanced-topics/launcher-api.adoc[].
image::junit-platform-suite-engine-diagram.svg[role=text-center]
[[setup]]
== Setup
In addition to the `junit-platform-suite-api` and `junit-platform-suite-engine` artifacts,
you need _at least one_ other test engine and its dependencies on the classpath. See
xref:appendix.adoc#dependency-metadata[Dependency Metadata] for details regarding group IDs, artifact IDs, and versions.
[[setup-required-dependencies]]
=== Required Dependencies
* `junit-platform-suite-api` in _test_ scope: artifact containing annotations needed to
configure a test suite
* `junit-platform-suite-engine` in _test runtime_ scope: implementation of the
`TestEngine` API for declarative test suites
NOTE: Both of the required dependencies are aggregated in the `junit-platform-suite`
artifact which can be declared in _test_ scope instead of declaring explicit dependencies
on `junit-platform-suite-api` and `junit-platform-suite-engine`.
[[setup-transitive-dependencies]]
=== Transitive Dependencies
* `junit-platform-launcher` in _test_ scope
* `junit-platform-engine` in _test_ scope
* `junit-platform-commons` in _test_ scope
* `opentest4j` in _test_ scope
[[example]]
== @Suite Example
Annotate a class with `@Suite` to have it marked as a test suite on the JUnit Platform. As
seen in the following example, selector and filter annotations can be used to control the
contents of the suite.
[source,java,indent=0]
----
include::example$java/example/SuiteDemo.java[tags=user_guide]
----
.Additional Configuration Options
NOTE: There are numerous configuration options for discovering and filtering tests in a
test suite. Please consult the Javadoc of the `{suite-api-package}` package for a full
list of supported annotations and further details.
== @BeforeSuite and @AfterSuite
`@BeforeSuite` and `@AfterSuite` annotations can be used on methods inside a
`@Suite`-annotated class. They will be executed before and after all tests of the test
suite, respectively.
[source,java,indent=0]
----
include::example$java/example/BeforeAndAfterSuiteDemo.java[tags=user_guide]
----
[[duplicate-test-execution]]
== Duplicate Test Execution
Depending on the declared selectors, different suites may contain the same tests,
potentially with different configurations. Moreover, tests in a suite are executed in
addition to the tests executed by every other test engine, which can result in the same
tests being executed twice.
image::junit-platform-suite-engine-duplicate-test-execution-diagram.svg[role=text-center]
To prevent duplicate execution of tests within a suite, configure your build tool to
include only the `junit-platform-suite` engine, or use a custom naming pattern. For
example, name all suites `*Suite` and all tests `*Test`, and configure your build tool to
include only the former.
Alternatively, consider xref:running-tests/tags.adoc[using tags] to select specific groups of
tests.
================================================
FILE: documentation/modules/ROOT/pages/advanced-topics/launcher-api.adoc
================================================
= JUnit Platform Launcher API
One of the prominent goals of the JUnit Platform is to make the interface between JUnit
and its programmatic clients – build tools and IDEs – more powerful and stable. The
purpose is to decouple the internals of discovering and executing tests from all the
filtering and configuration that is necessary from the outside.
JUnit Platform provides a `Launcher` API that can be used to discover, filter, and execute
tests. Moreover, third party test libraries – like Spock or Cucumber – can plug into the
JUnit Platform's launching infrastructure by providing a custom
xref:advanced-topics/engines.adoc[TestEngine].
image::launcher-api-diagram.svg[role=text-center]
The launcher API is in the `{junit-platform-launcher}` module.
An example consumer of the launcher API is the `{ConsoleLauncher}` in the
`{junit-platform-console}` project.
[[discovery]]
== Discovering Tests
Having _test discovery_ as a dedicated feature of the platform frees IDEs and build tools
from most of the difficulties they had to go through to identify test classes and test
methods in previous versions of JUnit.
Usage Example:
[source,java,indent=0]
----
include::example$java/example/UsingTheLauncherForDiscoveryDemo.java[tags=imports]
----
[source,java,indent=0]
----
include::example$java/example/UsingTheLauncherForDiscoveryDemo.java[tags=discovery]
----
You can select classes, methods, and all classes in a package or even search for all tests
in the class-path or module-path. Discovery takes place across all participating test
engines.
The resulting `TestPlan` is a hierarchical (and read-only) description of all engines,
classes, and test methods that fit the `LauncherDiscoveryRequest`. The client can
traverse the tree, retrieve details about a node, and get a link to the original source
(like class, method, or file position). Every node in the test plan has a _unique ID_
that can be used to invoke a particular test or group of tests.
Clients can register one or more `{LauncherDiscoveryListener}` implementations via the
`{LauncherDiscoveryRequestBuilder}` to gain insight into events that occur during test
discovery. By default, the builder registers an "abort on failure" listener that aborts
test discovery after the first discovery failure is encountered. The default
`LauncherDiscoveryListener` can be changed via the
`junit.platform.discovery.listener.default` xref:running-tests/configuration-parameters.adoc[configuration
parameter].
[[execution]]
== Executing Tests
To execute tests, clients can use the same `LauncherDiscoveryRequest` as in the discovery
phase or create a new request. Test progress and reporting can be achieved by registering
one or more `{TestExecutionListener}` implementations with the `Launcher` as in the
following example.
[source,java,indent=0]
----
include::example$java/example/UsingTheLauncherDemo.java[tags=execution]
----
There is no return value for the `execute()` method, but you can use a
`TestExecutionListener` to aggregate the results. For examples see the
`{SummaryGeneratingListener}`, `{LegacyXmlReportGeneratingListener}`, and
`{UniqueIdTrackingListener}`.
NOTE: All `TestExecutionListener` methods are called sequentially. Methods for start
events are called in registration order while methods for finish events are called in
reverse order.
Test case execution won't start before all `executionStarted` calls have returned.
[[engines-custom]]
== Registering a TestEngine
See the dedicated section on xref:advanced-topics/engines.adoc#registration[TestEngine registration] for
details.
[[post-discovery-filters-custom]]
== Registering a PostDiscoveryFilter
In addition to specifying post-discovery filters as part of a `{LauncherDiscoveryRequest}`
passed to the `{Launcher}` API, `{PostDiscoveryFilter}` implementations will be discovered
at runtime via Java's `{ServiceLoader}` mechanism and automatically applied by the
`Launcher` in addition to those that are part of the request.
For example, an `example.CustomTagFilter` class implementing `PostDiscoveryFilter` and
declared within the `/META-INF/services/org.junit.platform.launcher.PostDiscoveryFilter`
file is loaded and applied automatically.
[[launcher-session-listeners-custom]]
== Registering a LauncherSessionListener
Registered implementations of `{LauncherSessionListener}` are notified when a
`{LauncherSession}` is opened (before a `{Launcher}` first discovers and executes tests)
and closed (when no more tests will be discovered or executed). They can be registered
programmatically via the `{LauncherConfig}` that is passed to the `{LauncherFactory}`, or
they can be discovered at runtime via Java's `{ServiceLoader}` mechanism and automatically
registered with `LauncherSession` (unless automatic registration is disabled.)
[[launcher-session-listeners-tool-support]]
=== Tool Support
The following build tools and IDEs are known to provide full support for `LauncherSession`:
* Gradle 4.6 and later
* Maven Surefire/Failsafe 3.0.0-M6 and later
* IntelliJ IDEA 2017.3 and later
Other tools might also work but have not been tested explicitly.
[[launcher-session-listeners-tool-example-usage]]
=== Example Usage
A `LauncherSessionListener` is well suited for implementing once-per-JVM setup/teardown
behavior since it's called before the first and after the last test in a launcher session,
respectively. The scope of a launcher session depends on the used IDE or build tool but
usually corresponds to the lifecycle of the test JVM. A custom listener that starts an
HTTP server before executing the first test and stops it after the last test has been
executed, could look like this:
[source,java]
.src/test/java/example/session/GlobalSetupTeardownListener.java
----
package example.session;
include::example$java/example/session/GlobalSetupTeardownListener.java[tags=user_guide]
----
<1> Get the store from the launcher session
<2> Lazily create the HTTP server and put it into the store
<3> Start the HTTP server
It uses a wrapper class to ensure the server is stopped when the launcher session is
closed:
[source,java]
.src/test/java/example/session/CloseableHttpServer.java
----
package example.session;
include::example$java/example/session/CloseableHttpServer.java[tags=user_guide]
----
<1> The `close()` method is called when the launcher session is closed
<2> Stop the HTTP server
This sample uses the HTTP server implementation from the jdk.httpserver module that comes
with the JDK but would work similarly with any other server or resource. In order for the
listener to be picked up by JUnit Platform, you need to register it as a service by adding
a resource file with the following name and contents to your test runtime classpath (e.g.
by adding the file to `src/test/resources`):
[source]
.src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener
----
include::example$resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener[]
----
You can now use the resource from your test:
[source,java]
.src/test/java/example/session/HttpTests.java
----
package example.session;
include::example$java/example/session/HttpTests.java[tags=user_guide]
----
<1> Retrieve the HTTP server instance from the store
<2> Get the host string directly from the injected HTTP server instance
<3> Get the port number directly from the injected HTTP server instance
<4> Send a request to the server
<5> Check the status code of the response
[[launcher-interceptors-custom]]
== Registering a LauncherInterceptor
In order to intercept the creation of instances of `{Launcher}` and
`{LauncherSessionListener}` and calls to the `discover` and `execute` methods of the
former, clients can register custom implementations of `{LauncherInterceptor}` via Java's
`{ServiceLoader}` mechanism by setting the
`junit.platform.launcher.interceptors.enabled` xref:running-tests/configuration-parameters.adoc[configuration parameter] to `true`.
[NOTE]
====
Since interceptors are registered before the test run starts, the
`junit.platform.launcher.interceptors.enabled` _configuration parameter_ can only be
supplied as a JVM system property or via the JUnit Platform configuration file (see
xref:running-tests/configuration-parameters.adoc[] for details). This _configuration parameter_ cannot be
supplied in the `LauncherDiscoveryRequest` that is passed to the `{Launcher}`.
====
A typical use case is to create a custom interceptor to replace the `ClassLoader` used by
the JUnit Platform to load test classes and engine implementations.
[source,java]
----
include::example$java/example/CustomLauncherInterceptor.java[tags=user_guide]
----
[[launcher-discovery-listeners-custom]]
== Registering a LauncherDiscoveryListener
In addition to specifying discovery listeners as part of a `{LauncherDiscoveryRequest}` or
registering them programmatically via the `{Launcher}` API, custom
`LauncherDiscoveryListener` implementations can be discovered at runtime via Java's
`{ServiceLoader}` mechanism and automatically registered with the `Launcher` created via
the `{LauncherFactory}`.
For example, an `example.CustomLauncherDiscoveryListener` class implementing
`LauncherDiscoveryListener` and declared within the
`/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener` file is loaded
and registered automatically.
[[listeners-custom]]
== Registering a TestExecutionListener
In addition to the public `{Launcher}` API method for registering test execution listeners
programmatically, custom `{TestExecutionListener}` implementations will be discovered at
runtime via Java's `{ServiceLoader}` mechanism and automatically registered with the
`Launcher` created via the `{LauncherFactory}`.
For example, an `example.CustomTestExecutionListener` class implementing
`TestExecutionListener` and declared within the
`/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file is loaded and
registered automatically.
[[listeners-config]]
== Configuring a TestExecutionListener
When a `{TestExecutionListener}` is registered programmatically via the `{Launcher}` API,
the listener may provide programmatic ways for it to be configured -- for example, via its
constructor, setter methods, etc. However, when a `TestExecutionListener` is registered
automatically via Java's `ServiceLoader` mechanism (see
<>), there is no way for the user to directly configure the
listener. In such cases, the author of a `TestExecutionListener` may choose to make the
listener configurable via xref:running-tests/configuration-parameters.adoc[configuration parameters]. The
listener can then access the configuration parameters via the `TestPlan` supplied to the
`testPlanExecutionStarted(TestPlan)` and `testPlanExecutionFinished(TestPlan)` callback
methods. See the `{UniqueIdTrackingListener}` for an example.
[[listeners-custom-deactivation]]
== Deactivating a TestExecutionListener
Sometimes it can be useful to run a test suite _without_ certain execution listeners being
active. For example, you might have custom a `{TestExecutionListener}` that sends the test
results to an external system for reporting purposes, and while debugging you might not
want these _debug_ results to be reported. To do this, provide a pattern for the
`junit.platform.execution.listeners.deactivate` _configuration parameter_ to specify which
execution listeners should be deactivated (i.e. not registered) for the current test run.
[NOTE]
====
Only listeners registered via the `{ServiceLoader}` mechanism within the
`/META-INF/services/org.junit.platform.launcher.TestExecutionListener` file can be
deactivated. In other words, any `TestExecutionListener` registered explicitly via the
`{LauncherDiscoveryRequest}` cannot be deactivated via the
`junit.platform.execution.listeners.deactivate` _configuration parameter_.
In addition, since execution listeners are registered before the test run starts, the
`junit.platform.execution.listeners.deactivate` _configuration parameter_ can only be
supplied as a JVM system property or via the JUnit Platform configuration file (see
xref:running-tests/configuration-parameters.adoc[] for details). This _configuration parameter_ cannot be
supplied in the `LauncherDiscoveryRequest` that is passed to the `{Launcher}`.
====
[[listeners-custom-deactivation-pattern]]
=== Pattern Matching Syntax
Refer to xref:running-tests/configuration-parameters.adoc#pattern[Pattern Matching Syntax] for details.
[[launcher-config]]
== Configuring the Launcher
If you require fine-grained control over automatic detection and registration of test
engines and listeners, you may create an instance of `{LauncherConfig}` and supply that to
the `{LauncherFactory}`. Typically, an instance of `LauncherConfig` is created via the
built-in fluent _builder_ API, as demonstrated in the following example.
[source,java,indent=0]
----
include::example$java/example/UsingTheLauncherDemo.java[tags=launcherConfig]
----
[[dry-run-mode]]
== Dry-Run Mode
When running tests via the `{Launcher}` API, you can enable _dry-run mode_ by setting the
`junit.platform.execution.dryRun.enabled` xref:running-tests/configuration-parameters.adoc[configuration parameter] to `true`. In this mode, the `{Launcher}` will not actually
execute any tests but will notify registered `{TestExecutionListener}` instances as if all
tests had been skipped and their containers had been successful. This can be useful to
test changes in the configuration of a build or to verify a listener is called as expected
without having to wait for all tests to be executed.
[[managing-state-across-test-engines]]
== Managing State Across Test Engines
When running tests on the JUnit Platform, multiple test engines may need to access shared
resources. Rather than initializing these resources multiple times, JUnit Platform
provides mechanisms to share state across test engines efficiently. Test engines can use
the Platform's `{NamespacedHierarchicalStore}` API to lazily initialize and share
resources, ensuring they are created only once regardless of execution order. Any resource
that is put into the store and implements `AutoCloseable` will be closed automatically when
the execution is finished.
TIP: The Jupiter engine allows read and write access to such resources via its
`{ExtensionContext_Store}` API.
The following example demonstrates two custom test engines sharing a `ServerSocket`
resource. `FirstCustomEngine` attempts to retrieve an existing `ServerSocket` from the
global store or creates a new one if it doesn't exist:
[source,java]
----
include::example$java/example/FirstCustomEngine.java[tags=user_guide]
----
`SecondCustomEngine` follows the same pattern, ensuring that regardless whether it runs
before or after `FirstCustomEngine`, it will use the same socket instance:
[source,java]
----
include::example$java/example/SecondCustomEngine.java[tags=user_guide]
----
TIP: In this case, the `ServerSocket` can be stored directly in the global store while
ensuring since it gets closed because it implements `AutoCloseable`. If you need to use a
type that does not do so, you can wrap it in a custom class that implements
`AutoCloseable` and delegates to the original type. This is important to ensure that the
resource is closed properly when the test run is finished.
For illustration, the following test verifies that both engines are sharing the same
`ServerSocket` instance and that it's closed after `Launcher.execute()` returns:
[source,java,indent=0]
----
include::example$java/example/sharedresources/SharedResourceDemo.java[tags=user_guide]
----
By using the Platform's `{NamespacedHierarchicalStore}` API with shared namespaces in this
way, test engines can coordinate resource creation and sharing without direct dependencies
between them.
Alternatively, it's possible to inject resources into test engines by
<>.
[[launcher-cancellation]]
== Cancelling a Running Test Execution
The launcher API provides the ability to cancel a running test execution mid-flight while
allowing engines to clean up resources. To request an execution to be cancelled, you need
to call `cancel()` on the `{CancellationToken}` that is passed to `Launcher.execute` as
part of the `{LauncherExecutionRequest}`.
For example, implementing a listener that cancels test execution after the first test
failed can be achieved as follows.
[source,java,indent=0]
----
include::example$java/example/UsingTheLauncherDemo.java[tags=cancellation-direct]
----
<1> Create a `{CancellationToken}`
<2> Implement a `{TestExecutionListener}` that calls `cancel()` when a test fails
<3> Register the cancellation token
<4> Register the listener
<5> Pass the `{LauncherExecutionRequest}` to `Launcher.execute`
[NOTE]
.Test Engine Support for Cancellation
====
Cancelling tests relies on xref:advanced-topics/engines.adoc[] checking and responding to the
`{CancellationToken}` appropriately (see
xref:advanced-topics/engines.adoc#requirements-cancellation[Test Engine Requirements] for details). The
`Launcher` will also check the token and cancel test execution when multiple test engines
are present at runtime.
At the time of writing, the following test engines support cancellation:
* `{junit-jupiter-engine}`
* `{junit-vintage-engine}`
* `{junit-platform-suite-engine}`
* https://github.com/junit-team/testng-engine[`testng-engine`] (version 1.1.0 and later)
* Any `{TestEngine}` extending `{HierarchicalTestEngine}` such as Spock and Cucumber
====
[[launcher-cancellation-execution-request-from-discovery-request]]
=== Building a LauncherExecutionRequest from a LauncherDiscoveryRequest
Rather than building a `{LauncherExecutionRequest}` by starting with a
`{LauncherDiscoveryRequestBuilder}` and calling `forExecution()`, you can create a
`{LauncherExecutionRequestBuilder}` from a previously built `{LauncherDiscoveryRequest}`.
[source,java,indent=0]
----
include::example$java/example/UsingTheLauncherDemo.java[tags=cancellation-discovery-request]
----
<1> Build a `{LauncherDiscoveryRequest}`
<2> Create a `{LauncherExecutionRequestBuilder}` with it
<3> Register the cancellation token
<4> Register the listener
<5> Pass the `{LauncherExecutionRequest}` to `Launcher.execute`
[[launcher-cancellation-execution-request-from-test-plan]]
=== Building a LauncherExecutionRequest from a TestPlan
Alternatively, a `{LauncherExecutionRequestBuilder}` can be created from a previously
discovered `{TestPlan}`.
[source,java,indent=0]
----
include::example$java/example/UsingTheLauncherDemo.java[tags=cancellation-test-plan]
----
<1> Build a `{LauncherDiscoveryRequest}`
<2> Discover a `{TestPlan}` via `Launcher.discover`
<3> Create a `{LauncherExecutionRequestBuilder}` with it
<4> Register the cancellation token
<5> Register the listener
<6> Pass the `{LauncherExecutionRequest}` to `Launcher.execute`
================================================
FILE: documentation/modules/ROOT/pages/advanced-topics/testkit.adoc
================================================
= JUnit Platform Test Kit
The `junit-platform-testkit` artifact provides support for executing a test plan on the
JUnit Platform and then verifying the expected results. This support is currently limited
to the execution of a single `TestEngine` (see <>).
[[engine]]
== Engine Test Kit
The `{testkit-engine-package}` package provides support for discovering and executing a
`{TestPlan}` for a given `{TestEngine}` running on the JUnit Platform and then accessing
the results via convenient result objects. For execution, a fluent API may be used to
verify the expected execution events were received. The key entry point into this API is
the `{EngineTestKit}` which provides static factory methods named `engine()`,
`discover()`, and `execute()`. It is recommended that you select one of the `engine()`
variants to benefit from the fluent API for building a `LauncherDiscoveryRequest`.
NOTE: If you prefer to use the `LauncherDiscoveryRequestBuilder` from the `Launcher` API
to build your `LauncherDiscoveryRequest`, you must use one of the `discover()` or
`execute()` variants in `EngineTestKit`.
The following test class written using JUnit Jupiter will be used in subsequent examples.
[[engine-ExampleTestCase]]
[source,java,indent=0]
----
include::example$java/example/ExampleTestCase.java[tags=user_guide]
----
For the sake of brevity, the following sections demonstrate how to test JUnit's own
`JupiterTestEngine` whose unique engine ID is `"junit-jupiter"`. If you want to test your
own `TestEngine` implementation, you need to use its unique engine ID. Alternatively, you
may test your own `TestEngine` by supplying an instance of it to the
`EngineTestKit.engine(TestEngine)` static factory method.
[[engine-discovery]]
== Verifying Test Discovery
The following test demonstrates how to verify that a `TestPlan` was discovered as expected
by the JUnit Jupiter `TestEngine`.
[source,java,indent=0]
----
include::example$java/example/testkit/EngineTestKitDiscoveryDemo.java[tags=user_guide]
----
<1> Select the JUnit Jupiter `TestEngine`.
<2> Select the <> test class.
<3> Discover the `TestPlan`.
<4> Assert engine root descriptor has expected display name.
<5> Assert no discovery issues were encountered.
[[engine-statistics]]
== Asserting Execution Statistics
One of the most common features of the Test Kit is the ability to assert statistics
against events fired during the execution of a `TestPlan`. The following tests demonstrate
how to assert statistics for _containers_ and _tests_ in the JUnit Jupiter `TestEngine`.
For details on what statistics are available, consult the Javadoc for `{EventStatistics}`.
[source,java,indent=0]
----
include::example$java/example/testkit/EngineTestKitStatisticsDemo.java[tags=user_guide]
----
<1> Select the JUnit Jupiter `TestEngine`.
<2> Select the <> test class.
<3> Execute the `TestPlan`.
<4> Filter by _container_ events.
<5> Assert statistics for _container_ events.
<6> Filter by _test_ events.
<7> Assert statistics for _test_ events.
NOTE: In the `verifyJupiterContainerStats()` test method, the counts for the `started` and
`succeeded` statistics are `2` since the `JupiterTestEngine` and the
<> class are both considered containers.
[[engine-events]]
== Asserting Events
If you find that <> alone is insufficient
for verifying the expected behavior of test execution, you can work directly with the
recorded `{Event}` elements and perform assertions against them.
For example, if you want to verify the reason that the `skippedTest()` method in
<> was skipped, you can do that as
follows.
[TIP]
====
The `assertThatEvents()` method in the following example is a shortcut for
`org.assertj.core.api.Assertions.assertThat(events.list())` from the {AssertJ} assertion
library.
For details on what _conditions_ are available for use with AssertJ assertions against
events, consult the Javadoc for `{EventConditions}`.
====
[source,java,indent=0]
----
include::example$java/example/testkit/EngineTestKitSkippedMethodDemo.java[tags=user_guide]
----
<1> Select the JUnit Jupiter `TestEngine`.
<2> Select the `skippedTest()` method in the <> test class.
<3> Execute the `TestPlan`.
<4> Filter by _test_ events.
<5> Save the _test_ `Events` to a local variable.
<6> Optionally assert the expected statistics.
<7> Assert that the recorded _test_ events contain exactly one skipped test named
`skippedTest` with `"for demonstration purposes"` as the _reason_.
If you want to verify the type of exception thrown from the `failingTest()` method in
<>, you can do that as follows.
[TIP]
====
For details on what _conditions_ are available for use with AssertJ assertions against
events and execution results, consult the Javadoc for `{EventConditions}` and
`{TestExecutionResultConditions}`, respectively.
====
[source,java,indent=0]
----
include::example$java/example/testkit/EngineTestKitFailedMethodDemo.java[tags=user_guide]
----
<1> Select the JUnit Jupiter `TestEngine`.
<2> Select the <> test class.
<3> Execute the `TestPlan`.
<4> Filter by _test_ events.
<5> Assert that the recorded _test_ events contain exactly one failing test named
`failingTest` with an exception of type `ArithmeticException` and an error message
that ends with `"/ by zero"`.
Although typically unnecessary, there are times when you need to verify **all** of the
events fired during the execution of a `TestPlan`. The following test demonstrates how to
achieve this via the `assertEventsMatchExactly()` method in the `EngineTestKit` API.
[TIP]
====
Since `assertEventsMatchExactly()` matches conditions exactly in the order in which the
events were fired, <> has been
annotated with `@TestMethodOrder(OrderAnnotation.class)` and each test method has been
annotated with `@Order(...)`. This allows us to enforce the order in which the test
methods are executed, which in turn allows our `verifyAllJupiterEvents()` test to be
reliable.
====
If you want to do a _partial_ match _with_ or _without_ ordering requirements, you can use
the methods `assertEventsMatchLooselyInOrder()` and `assertEventsMatchLoosely()`,
respectively.
[source,java,indent=0]
----
include::example$java/example/testkit/EngineTestKitAllEventsDemo.java[tags=user_guide]
----
<1> Select the JUnit Jupiter `TestEngine`.
<2> Select the <> test class.
<3> Execute the `TestPlan`.
<4> Filter by _all_ events.
<5> Print all events to the supplied `writer` for debugging purposes. Debug information
can also be written to an `OutputStream` such as `System.out` or `System.err`.
<6> Assert _all_ events in exactly the order in which they were fired by the test engine.
The `debug()` invocation from the preceding example results in output similar to the
following.
[source,options="nowrap"]
----
All Events:
Event [type = STARTED, testDescriptor = JupiterEngineDescriptor: [engine:junit-jupiter], timestamp = 2018-12-14T12:45:14.082280Z, payload = null]
Event [type = STARTED, testDescriptor = ClassTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase], timestamp = 2018-12-14T12:45:14.089339Z, payload = null]
Event [type = SKIPPED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:skippedTest()], timestamp = 2018-12-14T12:45:14.094314Z, payload = 'for demonstration purposes']
Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:succeedingTest()], timestamp = 2018-12-14T12:45:14.095182Z, payload = null]
Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:succeedingTest()], timestamp = 2018-12-14T12:45:14.104922Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]]
Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:abortedTest()], timestamp = 2018-12-14T12:45:14.106121Z, payload = null]
Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:abortedTest()], timestamp = 2018-12-14T12:45:14.109956Z, payload = TestExecutionResult [status = ABORTED, throwable = org.opentest4j.TestAbortedException: Assumption failed: abc does not contain Z]]
Event [type = STARTED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:failingTest()], timestamp = 2018-12-14T12:45:14.110680Z, payload = null]
Event [type = FINISHED, testDescriptor = TestMethodTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase]/[method:failingTest()], timestamp = 2018-12-14T12:45:14.111217Z, payload = TestExecutionResult [status = FAILED, throwable = java.lang.ArithmeticException: / by zero]]
Event [type = FINISHED, testDescriptor = ClassTestDescriptor: [engine:junit-jupiter]/[class:example.ExampleTestCase], timestamp = 2018-12-14T12:45:14.113731Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]]
Event [type = FINISHED, testDescriptor = JupiterEngineDescriptor: [engine:junit-jupiter], timestamp = 2018-12-14T12:45:14.113806Z, payload = TestExecutionResult [status = SUCCESSFUL, throwable = null]]
----
================================================
FILE: documentation/modules/ROOT/pages/api-evolution.adoc
================================================
= API Evolution
:page-toclevels: 1
One of the major goals of the JUnit Platform architecture is to improve maintainers'
capabilities to evolve JUnit despite its being used in many projects. With JUnit 4 a lot
of stuff that was originally added as an internal construct only got used by external
extension writers and tool builders. That made changing JUnit 4 especially difficult and
sometimes impossible.
That's why JUnit now uses a defined lifecycle for all publicly available interfaces,
classes, and methods.
[[version-and-status]]
== API Version and Status
Every published artifact has a version number `..`, and all publicly
available interfaces, classes, and methods are annotated with {API} from the
{API_Guardian} project. The annotation's `status` attribute can be assigned one of the
following values.
[cols="20,80"]
|===
| Status | Description
| `INTERNAL` | Must not be used by any code other than JUnit itself. Might be removed without prior notice.
| `DEPRECATED` | Should no longer be used; might disappear in the next minor release.
| `EXPERIMENTAL` | Intended for new, experimental features where we are looking for feedback. +
Use this element with caution; it might be promoted to `MAINTAINED` or
`STABLE` in the future, but might also be removed without prior notice, even in a patch.
| `MAINTAINED` | Intended for features that will not be changed in a backwards-
incompatible way for *at least* the next minor release of the current
major version. If scheduled for removal, it will be demoted to `DEPRECATED` first.
| `STABLE` | Intended for features that will not be changed in a backwards-
incompatible way in the current major version (`6.*`).
|===
If the `@API` annotation is present on a type, it is considered to be applicable for all
public members of that type as well. A member is allowed to declare a different `status`
value of lower stability.
[[experimental-apis]]
== Experimental APIs
The following tables list which APIs are currently designated as _experimental_ via
`@API(status = EXPERIMENTAL)`. Caution should be taken when relying on such APIs.
include::partial$experimental-apis-table.adoc[]
[[deprecated-apis]]
== Deprecated APIs
The following tables list which APIs are currently designated as _deprecated_ via
`@API(status = DEPRECATED)`. You should avoid using deprecated APIs whenever possible,
since such APIs will likely be removed in an upcoming release.
include::partial$deprecated-apis-table.adoc[]
[[tooling]]
== @API Tooling Support
The {API_Guardian} project plans to provide tooling support for publishers and consumers
of APIs annotated with {API}. For example, the tooling support will likely provide a
means to check if JUnit APIs are being used in accordance with `@API` annotation
declarations.
================================================
FILE: documentation/modules/ROOT/pages/appendix.adoc
================================================
= Appendix
[[reproducible-builds]]
== Reproducible Builds
JUnit aims for its non-javadoc JARs to be https://reproducible-builds.org/[reproducible].
Under identical build conditions, such as Java version, repeated builds should provide the
same output byte-for-byte.
This means that anyone can reproduce the build conditions of the artifacts on Maven
Central/Sonatype and produce the same output artifact locally, confirming that the
artifacts in the repositories were actually generated from this source code.
[[dependency-metadata]]
== Dependency Metadata
Artifacts for final releases and milestones are deployed to {Maven_Central}. Consult
https://central.sonatype.org/consume/[Sonatype's documentation] for how to consume those
artifacts with a build tool of your choice.
Snapshot artifacts are deployed to Sonatype's {snapshot-repo}[snapshots repository]
under {snapshot-repo}/org/junit/[/org/junit]. Please refer to
https://central.sonatype.org/publish/publish-portal-snapshots/#consuming-snapshot-releases-for-your-project[Sonatype's documentation]
for instructions on how to consume them with a build tool of your choice.
The sections below list all artifacts with their versions for the three groups:
<>,
<>, and
<>.
The <> contains a list of all
of the above artifacts and their versions.
[TIP]
.Aligning dependency versions
====
To ensure that all JUnit artifacts are compatible with each other, their versions should
be aligned.
If you rely on xref:running-tests/build-support.adoc#spring-boot[Spring Boot] for dependency management,
please see the corresponding section.
Otherwise, instead of managing individual versions of the JUnit artifacts, it is
recommended to apply the <> to your project.
Please refer to the corresponding sections for xref:running-tests/build-support.adoc#maven-bom[Maven] or
xref:running-tests/build-support.adoc#gradle-bom[Gradle].
====
[[dependency-metadata-junit-platform]]
=== JUnit Platform
* *Group ID*: `org.junit.platform`
* *Version*: `{version}`
* *Artifact IDs*:
`junit-platform-commons`::
Common APIs and support utilities for the JUnit Platform. Any API annotated with
`@API(status = INTERNAL)` is intended solely for usage within the JUnit framework
itself. _Any usage of internal APIs by external parties is not supported!_
`junit-platform-console`::
Support for discovering and executing tests on the JUnit Platform from the console.
See xref:running-tests/console-launcher.adoc[] for details.
`junit-platform-console-standalone`::
An executable _Fat JAR_ that contains all dependencies is provided in Maven Central under the
https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone[junit-platform-console-standalone]
directory. See xref:running-tests/console-launcher.adoc[] for details.
`junit-platform-engine`::
Public API for test engines. See xref:advanced-topics/launcher-api.adoc#engines-custom[Registering a TestEngine] for details.
`junit-platform-launcher`::
Public API for configuring and launching test plans -- typically used by IDEs and
build tools. See xref:advanced-topics/launcher-api.adoc[] for details.
`junit-platform-reporting`::
`TestExecutionListener` implementations that generate test reports -- typically used
by IDEs and build tools. See xref:advanced-topics/junit-platform-reporting.adoc[] for details.
`junit-platform-suite`::
JUnit Platform Suite artifact that transitively pulls in dependencies on
`junit-platform-suite-api` and `junit-platform-suite-engine` for simplified dependency
management in build tools such as Gradle and Maven.
`junit-platform-suite-api`::
Annotations for configuring test suites on the JUnit Platform. Supported by the
xref:advanced-topics/junit-platform-suite-engine.adoc[JUnit Platform Suite Engine].
`junit-platform-suite-engine`::
Engine that executes test suites on the JUnit Platform; only required at runtime. See
xref:advanced-topics/junit-platform-suite-engine.adoc[JUnit Platform Suite Engine] for details.
`junit-platform-testkit`::
Provides support for executing a test plan for a given `TestEngine` and then
accessing the results via a fluent API to verify the expected results.
[[dependency-metadata-junit-jupiter]]
=== JUnit Jupiter
* *Group ID*: `org.junit.jupiter`
* *Version*: `{version}`
* *Artifact IDs*:
`junit-jupiter`::
JUnit Jupiter aggregator artifact that transitively pulls in dependencies on
`junit-jupiter-api`, `junit-jupiter-params`, and `junit-jupiter-engine` for
simplified dependency management in build tools such as Gradle and Maven.
`junit-jupiter-api`::
JUnit Jupiter API for xref:writing-tests/intro.adoc[writing tests] and xref:extensions/overview.adoc[extensions].
`junit-jupiter-engine`::
JUnit Jupiter test engine implementation; only required at runtime.
`junit-jupiter-params`::
Support for xref:writing-tests/parameterized-classes-and-tests.adoc[] in JUnit Jupiter.
`junit-jupiter-migrationsupport`::
_Deprecated_ support for migrating from JUnit 4 to JUnit Jupiter; only required for
support for JUnit 4's `@Ignore` annotation and for running selected JUnit 4 rules.
[[dependency-metadata-junit-vintage]]
=== JUnit Vintage
* *Group ID*: `org.junit.vintage`
* *Version*: `{version}`
* *Artifact ID*:
`junit-vintage-engine`::
JUnit Vintage test engine implementation that allows one to run _vintage_ JUnit tests
on the JUnit Platform. _Vintage_ tests include those written using JUnit 3 or JUnit 4
APIs or tests written using testing frameworks built on those APIs.
[[dependency-metadata-junit-bom]]
=== Bill of Materials (BOM)
The _Bill of Materials_ POM provided under the following Maven coordinates can be used to
ease dependency management when referencing multiple of the above artifacts using
xref:running-tests/build-support.adoc#maven-bom[Maven] or
xref:running-tests/build-support.adoc#gradle-bom[Gradle].
* *Group ID*: `org.junit`
* *Artifact ID*: `junit-bom`
* *Version*: `{version}`
[[dependency-metadata-dependencies]]
=== Dependencies
Most of the above artifacts have a dependency in their published Maven POMs on the
following _@API Guardian_ JAR.
* *Group ID*: `org.apiguardian`
* *Artifact ID*: `apiguardian-api`
* *Version*: `{apiguardian-version}`
In addition, most of the above artifacts have a direct or transitive dependency on the
following _OpenTest4J_ JAR.
* *Group ID*: `org.opentest4j`
* *Artifact ID*: `opentest4j`
* *Version*: `{ota4j-version}`
[[dependency-diagram]]
== Dependency Diagram
image::component-diagram.svg[]
================================================
FILE: documentation/modules/ROOT/pages/extensions/conditional-test-execution.adoc
================================================
= Conditional Test Execution
`{ExecutionCondition}` defines the `Extension` API for programmatic, _conditional test
execution_.
An `ExecutionCondition` is _evaluated_ for each container (e.g., a test class) to
determine if all the tests it contains should be executed based on the supplied
`ExtensionContext`. Similarly, an `ExecutionCondition` is _evaluated_ for each test to
determine if a given test method should be executed based on the supplied
`ExtensionContext`.
When multiple `ExecutionCondition` extensions are registered, a container or test is
disabled as soon as one of the conditions returns _disabled_. Thus, there is no guarantee
that a condition is evaluated because another extension might have already caused a
container or test to be disabled. In other words, the evaluation works like the
short-circuiting boolean OR operator.
See the source code of `{DisabledCondition}` and `{Disabled}` for concrete examples.
[[deactivation]]
== Deactivating Conditions
Sometimes it can be useful to run a test suite _without_ certain conditions being active.
For example, you may wish to run tests even if they are annotated with `@Disabled` in
order to see if they are still _broken_. To do this, provide a pattern for the
`junit.jupiter.conditions.deactivate` _configuration parameter_ to specify which
conditions should be deactivated (i.e., not evaluated) for the current test run. The
pattern can be supplied as a JVM system property, as a _configuration parameter_ in the
`LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform
configuration file (see xref:running-tests/configuration-parameters.adoc[] for details).
For example, to deactivate JUnit's `@Disabled` condition, you can start your JVM with the
following system property.
`-Djunit.jupiter.conditions.deactivate=org.junit.*DisabledCondition`
[[deactivation-patterns]]
=== Pattern Matching Syntax
Refer to xref:running-tests/configuration-parameters.adoc#pattern[Pattern Matching Syntax] for details.
================================================
FILE: documentation/modules/ROOT/pages/extensions/exception-handling.adoc
================================================
= Exception Handling
Exceptions thrown during the test execution may be intercepted and handled accordingly
before propagating further, so that certain actions like error logging or resource releasing
may be defined in specialized `Extensions`. JUnit Jupiter offers API for `Extensions` that
wish to handle exceptions thrown during `@Test` methods via `{TestExecutionExceptionHandler}`
and for those thrown during one of test lifecycle methods (`@BeforeAll`, `@BeforeEach`,
`@AfterEach` and `@AfterAll`) via `{LifecycleMethodExecutionExceptionHandler}`.
The following example shows an extension which will swallow all instances of `IOException`
but rethrow any other type of exception.
[source,java,indent=0]
.An exception handling extension that filters IOExceptions in test execution
----
include::example$java/example/exception/IgnoreIOExceptionExtension.java[tags=user_guide]
----
Another example shows how to record the state of an application under test exactly at
the point of unexpected exception being thrown during setup and cleanup. Note that unlike
relying on lifecycle callbacks, which may or may not be executed depending on the test
status, this solution guarantees execution immediately after failing `@BeforeAll`,
`@BeforeEach`, `@AfterEach` or `@AfterAll`.
[source,java,indent=0]
.An exception handling extension that records application state on error
----
include::example$java/example/exception/RecordStateOnErrorExtension.java[tags=user_guide]
----
Multiple execution exception handlers may be invoked for the same lifecycle method in
order of declaration. If one of the handlers swallows the handled exception, subsequent
ones will not be executed, and no failure will be propagated to JUnit engine, as if the
exception was never thrown. Handlers may also choose to rethrow the exception or throw
a different one, potentially wrapping the original.
Extensions implementing `{LifecycleMethodExecutionExceptionHandler}` that wish to handle
exceptions thrown during `@BeforeAll` or `@AfterAll` need to be registered on a class level,
while handlers for `BeforeEach` and `AfterEach` may be also registered for individual
test methods.
[source,java,indent=0]
.Registering multiple exception handling extensions
----
include::example$java/example/exception/MultipleHandlersTestCase.java[tags=user_guide]
----
================================================
FILE: documentation/modules/ROOT/pages/extensions/intercepting-invocations.adoc
================================================
= Intercepting Invocations
`{InvocationInterceptor}` defines the API for `Extensions` that wish to intercept calls to
test code.
The following example shows an extension that executes all test methods in Swing's Event
Dispatch Thread.
[source,java,indent=0]
.An extension that executes tests in a user-defined thread
----
include::example$java/example/interceptor/SwingEdtInterceptor.java[tags=user_guide]
----
[NOTE]
.Accessing the test-scoped `ExtensionContext`
====
You may override the `getTestInstantiationExtensionContextScope(...)` method to return
`TEST_METHOD` to make test-specific data available to your extension implementation of
`interceptTestClassConstructor` or if you want to xref:extensions/keeping-state-in-extensions.adoc[keep state]
on the test method level.
====
================================================
FILE: documentation/modules/ROOT/pages/extensions/keeping-state-in-extensions.adoc
================================================
= Keeping State in Extensions
Usually, an extension is instantiated only once. So the question becomes relevant: How do
you keep the state from one invocation of an extension to the next? The
`{ExtensionContext}` API provides a `{ExtensionContext_Store}` exactly for this purpose.
Extensions may put values into a store for later retrieval.
TIP: See the `xref:extensions/test-lifecycle-callbacks.adoc#timing-extension[TimingExtension]` for an
example of using the `Store` with a method-level scope.
.The `ExtensionContext` and `Store` hierarchy
image::extensions_StoreHierarchy.svg[alt=UML diagram,role=text-center]
As illustrated by the diagram above, stores are hierarchical in nature. When looking up a
value, if no value is stored in the current `ExtensionContext` for the supplied key, the
stores of the context's ancestors will be queried for a value with the same key in the
`Namespace` used to create this store. The root `ExtensionContext` represents the engine
level so its `Store` may be used to store or cache values that are used by multiple test
classes or extension. The `{ExtensionContext_StoreScope}` enum allows to go beyond even
that and access the stores on the level of the current `{LauncherExecutionRequest}` or
`{LauncherSession}` which can be used to share data across test engines or inject data
from a registered
xref:advanced-topics/launcher-api.adoc#launcher-session-listeners-custom[`LauncherSessionListener`],
respectively. Please consult the Javadoc of `{ExtensionContext}`,
`{ExtensionContext_Store}`, and `{ExtensionContext_StoreScope}` for details.
[[support]]
[NOTE]
.Resource management via `_AutoCloseable_`
====
An extension context store is bound to its extension context lifecycle. When an extension
context lifecycle ends it closes its associated store.
All stored values that are instances of `AutoCloseable` are notified by an invocation of
their `close()` method in the inverse order they were added in (unless the
`junit.jupiter.extensions.store.close.autocloseable.enabled`
xref:running-tests/configuration-parameters.adoc[configuration parameter] is set to `false`).
Versions prior to 5.13 only supported `CloseableResource`, which is deprecated but still
available for backward compatibility.
====
An example implementation of `AutoCloseable` is shown below, using an `HttpServer`
resource.
.`_HttpServer_` resource implementing `_AutoCloseable_`
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/extensions/HttpServerResource.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/extensions/HttpServerResource.kt[tags=user_guide]
----
--
====
This resource can then be stored in the desired `ExtensionContext`. It may be stored at
class or method level, if desired, but this may add unnecessary overhead for this type of
resource. For this example it might be prudent to store it at root level and instantiate
it lazily to ensure it's only created once per test run and reused across different test
classes and methods.
.Lazily storing in root context with `_Store.computeIfAbsent_`
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/extensions/HttpServerExtension.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/extensions/HttpServerExtension.kt[tags=user_guide]
----
--
====
.A test case using the `_HttpServerExtension_`
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/HttpServerDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/HttpServerDemo.kt[tags=user_guide]
----
--
====
[[migration]]
[TIP]
.Migration Note for Resource Cleanup
======
The framework automatically closes resources stored in the `ExtensionContext.Store` that
implement `AutoCloseable`. In versions prior to 5.13, only resources implementing
`Store.CloseableResource` were automatically closed.
If you're developing an extension that needs to support both JUnit Jupiter 5.13+ and
earlier versions and your extension stores resources that need to be cleaned up, you
should implement both interfaces:
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
public class MyResource implements Store.CloseableResource, AutoCloseable {
@Override
public void close() throws Exception {
// Resource cleanup code
}
}
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
class MyResource : Store.CloseableResource, AutoCloseable {
override fun close() {
// Resource cleanup code
}
}
----
--
====
This ensures that your resource will be properly closed regardless of which JUnit Jupiter
version is being used.
======
================================================
FILE: documentation/modules/ROOT/pages/extensions/overview.adoc
================================================
= Extension Model
In contrast to the competing `Runner`, `TestRule`, and `MethodRule` extension points in
JUnit 4, the JUnit Jupiter extension model consists of a single, coherent concept: the
`Extension` API. Note, however, that `Extension` itself is just a marker interface.
================================================
FILE: documentation/modules/ROOT/pages/extensions/parameter-resolution.adoc
================================================
= Parameter Resolution
`{ParameterResolver}` defines the `Extension` API for dynamically resolving parameters at
runtime.
If a _test class_ constructor, _test method_, or _lifecycle method_ (see
xref:writing-tests/definitions.adoc[]) declares a parameter, the parameter must be _resolved_ at
runtime by a `ParameterResolver`. A `ParameterResolver` can either be built-in (see
`{TestInfoParameterResolver}`) or xref:extensions/registering-extensions.adoc[registered by the user].
Generally speaking, parameters may be resolved by _name_, _type_, _annotation_, or any
combination thereof.
If you wish to implement a custom `{ParameterResolver}` that resolves parameters based
solely on the type of the parameter, you may find it convenient to extend the
`{TypeBasedParameterResolver}` which serves as a generic adapter for such use cases.
For concrete examples, consult the source code for `{CustomTypeParameterResolver}`,
`{CustomAnnotationParameterResolver}`, and `{MapOfListsTypeBasedParameterResolver}`.
[WARNING]
====
Due to a bug in the byte code generated by `javac` on JDK versions prior to JDK 9,
looking up annotations on parameters directly via the core `java.lang.reflect.Parameter`
API will always fail for _inner class_ constructors (e.g., a constructor in a `@Nested`
test class).
The `{ParameterContext}` API supplied to `ParameterResolver` implementations therefore
includes the following convenience methods for correctly looking up annotations on
parameters. Extension authors are strongly encouraged to use these methods instead of
those provided in `java.lang.reflect.Parameter` in order to avoid this bug in the JDK.
* `boolean isAnnotated(Class extends Annotation> annotationType)`
* `Optional findAnnotation(Class annotationType)`
* `List findRepeatableAnnotations(Class annotationType)`
====
[NOTE]
.Accessing the test-scoped `ExtensionContext`
====
You may override the `getTestInstantiationExtensionContextScope(...)` method to return
`TEST_METHOD` to support injecting test specific data into constructor parameters of the
test class instance. Doing so causes a test-specific `{ExtensionContext}` to be used while
resolving constructor parameters, unless the
xref:writing-tests/test-instance-lifecycle.adoc[test instance lifecycle] is set to `PER_CLASS`.
====
[TIP]
.Parameter resolution for methods called from extensions
====
Other extensions can also leverage registered `ParameterResolvers` for method and
constructor invocations, using the `{ExecutableInvoker}` available via the
`getExecutableInvoker()` method in the `ExtensionContext`.
====
[[conflicts]]
== Parameter Conflicts
If multiple implementations of `ParameterResolver` that support the same type are
registered for a test, a `ParameterResolutionException` will be thrown, with a
message to indicate that competing resolvers have been discovered. See the following
example:
.Conflicting parameter resolution due to multiple resolvers claiming support for integers
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/extensions/ParameterResolverConflictDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/extensions/ParameterResolverConflictDemo.kt[tags=user_guide]
----
--
====
If the conflicting `ParameterResolver` implementations are applied to different test
methods as shown in the following example, no conflict occurs.
.Fine-grained registration to avoid conflict
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/extensions/ParameterResolverNoConflictDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/extensions/ParameterResolverNoConflictDemo.kt[tags=user_guide]
----
--
====
If the conflicting `ParameterResolver` implementations need to be applied to the same test
method, you can implement a custom type or custom annotation as illustrated by
`{CustomTypeParameterResolver}` and `{CustomAnnotationParameterResolver}`, respectively.
.Custom type to resolve duplicate types
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/extensions/ParameterResolverCustomTypeDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/extensions/ParameterResolverCustomTypeDemo.kt[tags=user_guide]
----
--
====
A custom annotation makes the duplicate type distinguishable from its counterpart:
.Custom annotation to resolve duplicate types
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/extensions/ParameterResolverCustomAnnotationDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/extensions/ParameterResolverCustomAnnotationDemo.kt[tags=user_guide]
----
--
====
JUnit includes some built-in parameter resolvers that can cause conflicts if a resolver
attempts to claim their supported types. For example, `{TestInfo}` provides metadata about
tests. See xref:writing-tests/dependency-injection-for-constructors-and-methods.adoc[] for details. Third-party frameworks such
as Spring may also define parameter resolvers. Apply one of the techniques in this section
to resolve any conflicts.
Parameterized tests are another potential source of conflict. Ensure that tests annotated
with `@ParameterizedTest` are not also annotated with `@Test` and see
xref:writing-tests/parameterized-classes-and-tests.adoc#consuming-arguments[Consuming Arguments] for more details.
================================================
FILE: documentation/modules/ROOT/pages/extensions/pre-interrupt-callback.adoc
================================================
= Pre-Interrupt Callback
`{PreInterruptCallback}` defines the API for `Extensions` that wish to react on
timeouts before the `Thread.interrupt()` is called.
Please refer to xref:writing-tests/timeouts.adoc#debugging[Debugging Timeouts] for additional information.
================================================
FILE: documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-class-templates.adoc
================================================
= Providing Invocation Contexts for Class Templates
A `{ClassTemplate}` class can only be executed when at least one
`{ClassTemplateInvocationContextProvider}` is registered. Each such provider is
responsible for providing a `Stream` of `{ClassTemplateInvocationContext}` instances.
Each context may specify a custom display name and a list of additional extensions that
will only be used for the next invocation of the `{ClassTemplate}`.
The following example shows how to write a class template as well as how to register
and implement a `{ClassTemplateInvocationContextProvider}`.
[source,java,indent=0]
.A class template with accompanying extension
----
include::example$java/example/ClassTemplateDemo.java[tags=user_guide]
----
In this example, the class template will be invoked twice, meaning all test methods in
the class template will be executed twice. The display names of the invocations will be
`apple` and `banana` as specified by the invocation context. Each invocation registers a
custom `{TestInstancePostProcessor}` which is used to inject a value into a field. The
output when using the `ConsoleLauncher` is as follows.
....
└─ ClassTemplateDemo ✔
├─ apple ✔
│ ├─ notNull() ✔
│ └─ wellKnown() ✔
└─ banana ✔
├─ notNull() ✔
└─ wellKnown() ✔
....
The `{ClassTemplateInvocationContextProvider}` extension API is primarily intended for
implementing different kinds of tests that rely on repetitive invocation of _all_ test
methods in a test class albeit in different contexts — for example, with different
parameters, by preparing the test class instance differently, or multiple times without
modifying the context.
Please refer to the implementations of
xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Classes] which uses this extension
point to provide its functionality.
================================================
FILE: documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-test-templates.adoc
================================================
= Providing Invocation Contexts for Test Templates
A `{TestTemplate}` method can only be executed when at least one
`{TestTemplateInvocationContextProvider}` is registered. Each such provider is responsible
for providing a `Stream` of `{TestTemplateInvocationContext}` instances. Each context may
specify a custom display name and a list of additional extensions that will only be used
for the next invocation of the `{TestTemplate}` method.
The following example shows how to write a test template as well as how to register and
implement a `{TestTemplateInvocationContextProvider}`.
[source,java,indent=0]
.A test template with accompanying extension
----
include::example$java/example/TestTemplateDemo.java[tags=user_guide]
----
In this example, the test template will be invoked twice. The display names of the
invocations will be `apple` and `banana` as specified by the invocation context. Each
invocation registers a custom `{ParameterResolver}` which is used to resolve the method
parameter. The output when using the `ConsoleLauncher` is as follows.
....
└─ testTemplate(String) ✔
├─ apple ✔
└─ banana ✔
....
The `{TestTemplateInvocationContextProvider}` extension API is primarily intended for
implementing different kinds of tests that rely on repetitive invocation of a test-like
method albeit in different contexts — for example, with different parameters, by preparing
the test class instance differently, or multiple times without modifying the context.
Please refer to the implementations of xref:writing-tests/repeated-tests.adoc[] or
xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Tests] which use this extension point
to provide their functionality.
================================================
FILE: documentation/modules/ROOT/pages/extensions/registering-extensions.adoc
================================================
= Registering Extensions
Extensions can be registered _declaratively_ via
<>, _programmatically_ via
<>, or _automatically_ via
Java's <> mechanism.
[[registration-declarative]]
== Declarative Extension Registration
Developers can register one or more extensions _declaratively_ by annotating a test
interface, test class, test method, or custom _xref:writing-tests/annotations.adoc#annotations[composed
annotation]_ with `@ExtendWith(...)` and supplying class references for the extensions to
register. `@ExtendWith` may also be declared on fields or on parameters in test class
constructors, in test methods, and in `@BeforeAll`, `@AfterAll`, `@BeforeEach`, and
`@AfterEach` lifecycle methods.
For example, to register a `WebServerExtension` for a particular test method, you would
annotate the test method as follows. We assume the `WebServerExtension` starts a local web
server and injects the server's URL into parameters annotated with `@WebServerUrl`.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
@Test
@ExtendWith(WebServerExtension.class)
void getProductList(@WebServerUrl String serverUrl) {
WebClient webClient = new WebClient();
// Use WebClient to connect to web server using serverUrl and verify response
assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus());
}
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
@Test
@ExtendWith(WebServerExtension::class)
fun getProductList(@WebServerUrl serverUrl: String) {
val webClient = WebClient()
// Use WebClient to connect to web server using serverUrl and verify response
assertEquals(200, webClient.get("$serverUrl/products").responseStatus)
}
----
--
====
To register the `WebServerExtension` for all tests in a particular class and its
subclasses, you would annotate the test class as follows.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
@ExtendWith(WebServerExtension.class)
class MyTests {
// ...
}
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
@ExtendWith(WebServerExtension::class)
class MyTests {
// ...
}
----
--
====
Multiple extensions can be registered together like this:
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })
class MyFirstTests {
// ...
}
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
@ExtendWith(DatabaseExtension::class, WebServerExtension::class)
class MyFirstTests {
// ...
}
----
--
====
As an alternative, multiple extensions can be registered separately like this:
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
@ExtendWith(DatabaseExtension.class)
@ExtendWith(WebServerExtension.class)
class MySecondTests {
// ...
}
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
@ExtendWith(DatabaseExtension::class)
@ExtendWith(WebServerExtension::class)
class MySecondTests {
// ...
}
----
--
====
[TIP]
.Extension Registration Order
====
Extensions registered declaratively via `@ExtendWith` at the class level, method level, or
parameter level will be executed in the order in which they are declared in the source
code. For example, the execution of tests in both `MyFirstTests` and `MySecondTests` will
be extended by the `DatabaseExtension` and `WebServerExtension`, **in exactly that order**.
====
If you wish to combine multiple extensions in a reusable way, you can define a custom
_xref:writing-tests/annotations.adoc#annotations[composed annotation]_ and use `@ExtendWith` as a
_meta-annotation_ as in the following code listing. Then `@DatabaseAndWebServerExtension`
can be used in place of `@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })`.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })
public @interface DatabaseAndWebServerExtension {
}
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(DatabaseExtension::class, WebServerExtension::class)
annotation class DatabaseAndWebServerExtension
----
--
====
The above examples demonstrate how `@ExtendWith` can be applied at the class level or at
the method level; however, for certain use cases it makes sense for an extension to be
registered declaratively at the field or parameter level. Consider a
`RandomNumberExtension` which generates random numbers that can be injected into a field or
via a parameter in a constructor, test method, or lifecycle method. If the extension
provides a `@Random` annotation that is meta-annotated with
`@ExtendWith(RandomNumberExtension.class)` (see listing below), the extension can be used
transparently as in the following `RandomNumberDemo` example.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/extensions/Random.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/extensions/Random.kt[tags=user_guide]
----
--
====
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/extensions/RandomNumberDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/extensions/RandomNumberDemo.kt[tags=user_guide]
----
--
====
[[RandomNumberExtension]]
The following code listing provides an example of how one might choose to implement such a
`RandomNumberExtension`. This implementation works for the use cases in
`RandomNumberDemo`; however, it may not prove robust enough to cover all use cases -- for
example, the random number generation support is limited to integers; it uses
`java.util.Random` instead of `java.security.SecureRandom`; etc. In any case, it is
important to note which extension APIs are implemented and for what reasons.
Specifically, `RandomNumberExtension` implements the following extension APIs:
- `BeforeAllCallback`: to support static field injection
- `TestInstancePostProcessor`: to support non-static field injection
- `ParameterResolver`: to support constructor and method injection
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/extensions/RandomNumberExtension.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/extensions/RandomNumberExtension.kt[tags=user_guide]
----
--
====
[TIP]
.Extension Registration Order for `@ExtendWith` on Fields
====
Extensions registered declaratively via `@ExtendWith` on fields will be ordered relative
to `@RegisterExtension` fields and other `@ExtendWith` fields using an algorithm that is
deterministic but intentionally nonobvious. However, `@ExtendWith` fields can be ordered
using the `@Order` annotation. See the <> tip for `@RegisterExtension` fields for details.
====
[TIP]
.Extension Inheritance
====
Extensions registered declaratively via `@ExtendWith` on fields in superclasses will be
inherited.
See <> for details.
====
NOTE: `@ExtendWith` fields may be either `static` or non-static. The documentation on
<> and
<> for
`@RegisterExtension` fields also applies to `@ExtendWith` fields.
[[registration-programmatic]]
== Programmatic Extension Registration
Developers can register extensions _programmatically_ by annotating fields in test classes
with `{RegisterExtension}`.
When an extension is registered _declaratively_ via
<>, it can typically only be configured
via annotations. In contrast, when an extension is registered via `@RegisterExtension`, it
can be configured _programmatically_ -- for example, in order to pass arguments to the
extension's constructor, a static factory method, or a builder API.
[[registration-programmatic-order]]
[TIP]
.Extension Registration Order
====
By default, extensions registered programmatically via `@RegisterExtension` or
declaratively via `@ExtendWith` on fields will be ordered using an algorithm that is
deterministic but intentionally nonobvious. This ensures that subsequent runs of a test
suite execute extensions in the same order, thereby allowing for repeatable builds.
However, there are times when extensions need to be registered in an explicit order. To
achieve that, annotate `@RegisterExtension` fields or `@ExtendWith` fields with `{Order}`.
Any `@RegisterExtension` field or `@ExtendWith` field not annotated with `@Order` will be
ordered using the _default_ order which has a value of `Integer.MAX_VALUE / 2`. This
allows `@Order` annotated extension fields to be explicitly ordered before or after
non-annotated extension fields. Extensions with an explicit order value less than the
default order value will be registered before non-annotated extensions. Similarly,
extensions with an explicit order value greater than the default order value will be
registered after non-annotated extensions. For example, assigning an extension an explicit
order value that is greater than the default order value allows _before_ callback
extensions to be registered last and _after_ callback extensions to be registered first,
relative to other programmatically registered extensions.
====
[TIP]
.Extension Inheritance
====
Extensions registered via `@RegisterExtension` or `@ExtendWith` on fields in superclasses
will be inherited.
See <> for details.
====
NOTE: `@RegisterExtension` fields must not be `null` (at evaluation time) but may be
either `static` or non-static.
[[registration-programmatic-static-fields]]
=== Static Fields
If a `@RegisterExtension` field is `static`, the extension will be registered after
extensions that are registered at the class level via `@ExtendWith`. Such _static
extensions_ are not limited in which extension APIs they can implement. Extensions
registered via static fields may therefore implement class-level and instance-level
extension APIs such as `BeforeAllCallback`, `AfterAllCallback`,
`TestInstancePostProcessor`, and `TestInstancePreDestroyCallback` as well as method-level
extension APIs such as `BeforeEachCallback`, etc.
In the following example, the `server` field in the test class is initialized
programmatically by using a builder pattern supported by the `WebServerExtension`. The
configured `WebServerExtension` will be automatically registered as an extension at the
class level -- for example, in order to start the server before all tests in the class
and then stop the server after all tests in the class have completed. In addition, static
lifecycle methods annotated with `@BeforeAll` or `@AfterAll` as well as `@BeforeEach`,
`@AfterEach`, and `@Test` methods can access the instance of the extension via the
`server` field if necessary.
[source,java,indent=0]
.Registering an extension via a static field in Java
----
include::example$java/example/registration/WebServerDemo.java[tags=user_guide]
----
[[registration-programmatic-static-fields-kotlin]]
==== Static Fields in Kotlin
The Kotlin programming language does not have the concept of a `static` field. However,
the compiler can be instructed to generate a `private static` field using the `@JvmStatic`
annotation in Kotlin. If you want the Kotlin compiler to generate a `public static` field,
you can use the `@JvmField` annotation instead.
The following example is a version of the `WebServerDemo` from the previous section that
has been ported to Kotlin.
[source,kotlin,indent=0]
.Registering an extension via a static field in Kotlin
----
include::example$kotlin/example/registration/KotlinWebServerDemo.kt[tags=user_guide]
----
[[registration-programmatic-instance-fields]]
=== Instance Fields
If a `@RegisterExtension` field is non-static (i.e., an instance field), the extension
will be registered after the test class has been instantiated and after each registered
`TestInstancePostProcessor` has been given a chance to post-process the test instance
(potentially injecting the instance of the extension to be used into the annotated
field). Thus, if such an _instance extension_ implements class-level or instance-level
extension APIs such as `BeforeAllCallback`, `AfterAllCallback`, or
`TestInstancePostProcessor`, those APIs will not be honored. Instance extensions will be
registered _before_ extensions that are registered at the method level via `@ExtendWith`.
In the following example, the `docs` field in the test class is initialized
programmatically by invoking a custom `lookUpDocsDir()` method and supplying the result
to the static `forPath()` factory method in the `DocumentationExtension`. The configured
`DocumentationExtension` will be automatically registered as an extension at the method
level. In addition, `@BeforeEach`, `@AfterEach`, and `@Test` methods can access the
instance of the extension via the `docs` field if necessary.
[tabs]
====
Java::
+
--
[source,java,indent=0]
.An extension registered via an instance field
----
include::example$java/example/registration/DocumentationDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
.An extension registered via an instance field
----
include::example$kotlin/example/kotlin/registration/DocumentationDemo.kt[tags=user_guide]
----
--
====
[[registration-automatic]]
== Automatic Extension Registration
In addition to <>
and <> support
using annotations, JUnit Jupiter also supports _global extension registration_ via Java's
`{ServiceLoader}` mechanism, allowing third-party extensions to be auto-detected and
automatically registered based on what is available in the classpath.
Specifically, a custom extension can be registered by supplying its fully qualified class
name in a file named `org.junit.jupiter.api.extension.Extension` within the
`/META-INF/services` folder in its enclosing JAR file.
[[registration-automatic-enabling]]
=== Enabling Automatic Extension Detection
Auto-detection is an advanced feature and is therefore not enabled by default. To enable
it, set the `junit.jupiter.extensions.autodetection.enabled` _configuration parameter_ to
`true`. This can be supplied as a JVM system property, as a _configuration parameter_ in
the `LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform
configuration file (see xref:running-tests/configuration-parameters.adoc[] for details).
For example, to enable auto-detection of extensions, you can start your JVM with the
following system property.
`-Djunit.jupiter.extensions.autodetection.enabled=true`
When auto-detection is enabled, extensions discovered via the `{ServiceLoader}` mechanism
will be added to the extension registry after JUnit Jupiter's global extensions (e.g.,
support for `TestInfo`, `TestReporter`, etc.).
[[registration-automatic-filtering]]
=== Filtering Auto-detected Extensions
The list of auto-detected extensions can be filtered using include and exclude patterns
via the following xref:running-tests/configuration-parameters.adoc[configuration parameters]:
`junit.jupiter.extensions.autodetection.include=`::
Comma-separated list of _include_ patterns for auto-detected extensions.
`junit.jupiter.extensions.autodetection.exclude=`::
Comma-separated list of _exclude_ patterns for auto-detected extensions.
Include patterns are applied _before_ exclude patterns. If both include and exclude
patterns are provided, only extensions that match at least one include pattern and do not
match any exclude pattern will be auto-detected.
See xref:running-tests/configuration-parameters.adoc#pattern[Pattern Matching Syntax] for details on the pattern syntax.
[[registration-inheritance]]
== Extension Inheritance
Registered extensions are inherited within test class hierarchies with top-down semantics.
Similarly, extensions registered at the class-level are inherited at the method-level.
This applies to all extensions, independent of how they are registered (declaratively or
programmatically).
This means that extensions registered declaratively via `@ExtendWith` on a superclass will
be registered before extensions registered declaratively via `@ExtendWith` on a subclass.
Similarly, extensions registered programmatically via `@RegisterExtension` or
`@ExtendWith` on fields in a superclass will be registered before extensions registered
programmatically via `@RegisterExtension` or `@ExtendWith` on fields in a subclass, unless
`@Order` is used to alter that behavior (see <> for details).
NOTE: A specific extension implementation can only be registered once for a given
extension context and its parent contexts. Consequently, any attempt to register a
duplicate extension implementation will be ignored.
================================================
FILE: documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc
================================================
= Relative Execution Order of User Code and Extensions
When executing a test class that contains one or more test methods, a number of extension
callbacks are called in addition to the user-supplied test and lifecycle methods.
NOTE: See also: xref:writing-tests/test-execution-order.adoc[]
[[overview]]
== User and Extension Code
The following diagram illustrates the relative order of user-supplied code and extension
code. User-supplied test and lifecycle methods are shown in orange, with callback code
implemented by extensions shown in blue. The grey box denotes the execution of a single
test method and will be repeated for every test method in the test class.
[[diagram]]
.User code and extension code
image::extensions_lifecycle.png[]
The following table further explains the sixteen steps in the
<> diagram.
. *interface* `*org.junit.jupiter.api.extension.BeforeAllCallback*` +
extension code executed before all tests of the container are executed
. *annotation* `*org.junit.jupiter.api.BeforeAll*` +
user code executed before all tests of the container are executed
. *interface* `*org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler
#handleBeforeAllMethodExecutionException*` +
extension code for handling exceptions thrown from `@BeforeAll` methods
. *interface* `*org.junit.jupiter.api.extension.BeforeClassTemplateInvocationCallback*` +
extension code executed before each class template invocation is executed (only applicable
if the test class is a xref:writing-tests/class-templates.adoc[class template])
. *interface* `*org.junit.jupiter.api.extension.BeforeEachCallback*` +
extension code executed before each test is executed
. *annotation* `*org.junit.jupiter.api.BeforeEach*` +
user code executed before each test is executed
. *interface* `*org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler
#handleBeforeEachMethodExecutionException*` +
extension code for handling exceptions thrown from `@BeforeEach` methods
. *interface* `*org.junit.jupiter.api.extension.BeforeTestExecutionCallback*` +
extension code executed immediately before a test is executed
. *annotation* `*org.junit.jupiter.api.Test*` +
user code of the actual test method
. *interface* `*org.junit.jupiter.api.extension.TestExecutionExceptionHandler*` +
extension code for handling exceptions thrown during a test
. *interface* `*org.junit.jupiter.api.extension.AfterTestExecutionCallback*` +
extension code executed immediately after test execution and its corresponding exception handlers
. *annotation* `*org.junit.jupiter.api.AfterEach*` +
user code executed after each test is executed
. *interface* `*org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler
#handleAfterEachMethodExecutionException*` +
extension code for handling exceptions thrown from `@AfterEach` methods
. *interface* `*org.junit.jupiter.api.extension.AfterEachCallback*` +
extension code executed after each test is executed
. *interface* `*org.junit.jupiter.api.extension.AfterClassTemplateInvocationCallback*` +
extension code executed after each class template invocation is executed (only applicable
if the test class is a xref:writing-tests/class-templates.adoc[class template])
. *annotation* `*org.junit.jupiter.api.AfterAll*` +
user code executed after all tests of the container are executed
. *interface* `*org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler
#handleAfterAllMethodExecutionException*` +
extension code for handling exceptions thrown from `@AfterAll` methods
. *interface* `*org.junit.jupiter.api.extension.AfterAllCallback*` +
extension code executed after all tests of the container are executed
In the simplest case only the actual test method will be executed (step 9); all other
steps are optional depending on the presence of user code or extension support for the
corresponding lifecycle callback. For further details on the various lifecycle callbacks
please consult the respective Javadoc for each annotation and extension.
All invocations of user code methods in the above table can additionally be intercepted
by implementing xref:extensions/intercepting-invocations.adoc[`InvocationInterceptor`].
[[wrapping-behavior]]
== Wrapping Behavior of Callbacks
JUnit Jupiter always guarantees _wrapping_ behavior for multiple registered extensions
that implement lifecycle callbacks such as `BeforeAllCallback`, `AfterAllCallback`,
`BeforeClassTemplateInvocationCallback`, `AfterClassTemplateInvocationCallback`,
`BeforeEachCallback`, `AfterEachCallback`, `BeforeTestExecutionCallback`, and
`AfterTestExecutionCallback`.
That means that, given two extensions `Extension1` and `Extension2` with `Extension1`
registered before `Extension2`, any "before" callbacks implemented by `Extension1` are
guaranteed to execute **before** any "before" callbacks implemented by `Extension2`.
Similarly, given the two same two extensions registered in the same order, any "after"
callbacks implemented by `Extension1` are guaranteed to execute **after** any "after"
callbacks implemented by `Extension2`. `Extension1` is therefore said to _wrap_
`Extension2`.
JUnit Jupiter also guarantees _wrapping_ behavior within class and interface hierarchies
for user-supplied _lifecycle methods_ (see xref:writing-tests/definitions.adoc[]).
* `@BeforeAll` methods are inherited from superclasses as long as they are not
_overridden_. Furthermore, `@BeforeAll` methods from superclasses will be executed
**before** `@BeforeAll` methods in subclasses.
** Similarly, `@BeforeAll` methods declared in an interface are inherited as long as they
are not _overridden_, and `@BeforeAll` methods from an interface will be executed
**before** `@BeforeAll` methods in the class that implements the interface.
* `@AfterAll` methods are inherited from superclasses as long as they are not
_overridden_. Furthermore, `@AfterAll` methods from superclasses will be executed
**after** `@AfterAll` methods in subclasses.
** Similarly, `@AfterAll` methods declared in an interface are inherited as long as they
are not _overridden_, and `@AfterAll` methods from an interface will be executed
**after** `@AfterAll` methods in the class that implements the interface.
* `@BeforeEach` methods are inherited from superclasses as long as they are not
_overridden_. Furthermore, `@BeforeEach` methods from superclasses will be executed
**before** `@BeforeEach` methods in subclasses.
** Similarly, `@BeforeEach` methods declared as interface default methods are inherited as
long as they are not _overridden_, and `@BeforeEach` default methods will be executed
**before** `@BeforeEach` methods in the class that implements the interface.
* `@AfterEach` methods are inherited from superclasses as long as they are not
_overridden_. Furthermore, `@AfterEach` methods from superclasses will be executed
**after** `@AfterEach` methods in subclasses.
** Similarly, `@AfterEach` methods declared as interface default methods are inherited as
long as they are not _overridden_, and `@AfterEach` default methods will be executed
**after** `@AfterEach` methods in the class that implements the interface.
The following examples demonstrate this behavior. Please note that the examples do not
actually do anything realistic. Instead, they mimic common scenarios for testing
interactions with the database. All methods imported statically from the `Logger` class
log contextual information in order to help us better understand the execution order of
user-supplied callback methods and callback methods in extensions.
[source,java,indent=0]
.Extension1
----
include::example$java/example/callbacks/Extension1.java[tags=user_guide]
----
[source,java,indent=0]
.Extension2
----
include::example$java/example/callbacks/Extension2.java[tags=user_guide]
----
[source,java,indent=0]
.AbstractDatabaseTests
----
include::example$java/example/callbacks/AbstractDatabaseTests.java[tags=user_guide]
----
[source,java,indent=0]
.DatabaseTestsDemo
----
include::example$java/example/callbacks/DatabaseTestsDemo.java[tags=user_guide]
----
When the `DatabaseTestsDemo` test class is executed, the following is logged.
----
@BeforeAll AbstractDatabaseTests.createDatabase()
@BeforeAll DatabaseTestsDemo.beforeAll()
Extension1.beforeEach()
Extension2.beforeEach()
@BeforeEach AbstractDatabaseTests.connectToDatabase()
@BeforeEach DatabaseTestsDemo.insertTestDataIntoDatabase()
@Test DatabaseTestsDemo.testDatabaseFunctionality()
@AfterEach DatabaseTestsDemo.deleteTestDataFromDatabase()
@AfterEach AbstractDatabaseTests.disconnectFromDatabase()
Extension2.afterEach()
Extension1.afterEach()
@BeforeAll DatabaseTestsDemo.afterAll()
@AfterAll AbstractDatabaseTests.destroyDatabase()
----
The following sequence diagram helps to shed further light on what actually goes on within
the `JupiterTestEngine` when the `DatabaseTestsDemo` test class is executed.
////
PNG generated using ZenUML: https://app.zenuml.com
See corresponding *.txt file in images folder for the source.
////
image::extensions_DatabaseTestsDemo.png[caption='',title='DatabaseTestsDemo']
JUnit Jupiter does **not** guarantee the execution order of multiple lifecycle methods
that are declared within a _single_ test class or test interface. It may at times appear
that JUnit Jupiter invokes such methods in alphabetical order. However, that is not
precisely true. The ordering is analogous to the ordering for `@Test` methods within a
single test class.
[NOTE]
====
Lifecycle methods that are declared within a _single_ test class or test interface will be
ordered using an algorithm that is deterministic but intentionally non-obvious. This
ensures that subsequent runs of a test suite execute lifecycle methods in the same order,
thereby allowing for repeatable builds.
====
In addition, JUnit Jupiter does **not** support _wrapping_ behavior for multiple lifecycle
methods declared within a single test class or test interface.
The following example demonstrates this behavior. Specifically, the lifecycle method
configuration is _broken_ due to the order in which the locally declared lifecycle methods
are executed.
* Test data is inserted _before_ the database connection has been opened, which results in
a failure to connect to the database.
* The database connection is closed _before_ deleting the test data, which results in a
failure to connect to the database.
[source,java,indent=0]
.BrokenLifecycleMethodConfigDemo
----
include::example$java/example/callbacks/BrokenLifecycleMethodConfigDemo.java[tags=user_guide]
----
When the `BrokenLifecycleMethodConfigDemo` test class is executed, the following is logged.
----
Extension1.beforeEach()
Extension2.beforeEach()
@BeforeEach BrokenLifecycleMethodConfigDemo.insertTestDataIntoDatabase()
@BeforeEach BrokenLifecycleMethodConfigDemo.connectToDatabase()
@Test BrokenLifecycleMethodConfigDemo.testDatabaseFunctionality()
@AfterEach BrokenLifecycleMethodConfigDemo.disconnectFromDatabase()
@AfterEach BrokenLifecycleMethodConfigDemo.deleteTestDataFromDatabase()
Extension2.afterEach()
Extension1.afterEach()
----
The following sequence diagram helps to shed further light on what actually goes on within
the `JupiterTestEngine` when the `BrokenLifecycleMethodConfigDemo` test class is executed.
////
PNG generated using ZenUML: https://app.zenuml.com
See corresponding *.txt file in images folder for the source.
////
image::extensions_BrokenLifecycleMethodConfigDemo.png[caption='',title='BrokenLifecycleMethodConfigDemo']
[TIP]
====
Due to the aforementioned behavior, the JUnit Team recommends that developers declare at
most one of each type of _lifecycle method_ (see xref:writing-tests/definitions.adoc[]) per test
class or test interface unless there are no dependencies between such lifecycle methods.
====
================================================
FILE: documentation/modules/ROOT/pages/extensions/supported-utilities-in-extensions.adoc
================================================
= Supported Utilities in Extensions
The `junit-platform-commons` artifact provides _maintained_ utilities for working with
annotations, classes, reflection, classpath scanning, and conversion tasks. These
utilities can be found in the `{junit-platform-support-package}` and its subpackages.
`TestEngine` and `Extension` authors are encouraged to use these supported utilities in
order to align with the behavior of the JUnit Platform and JUnit Jupiter.
[[annotations]]
== Annotation Support
`AnnotationSupport` provides static utility methods that operate on annotated elements
(e.g., packages, annotations, classes, interfaces, constructors, methods, and fields).
These include methods to check whether an element is annotated or meta-annotated with a
particular annotation, to search for specific annotations, and to find annotated methods
and fields in a class or interface. Some of these methods search on implemented
interfaces and within class hierarchies to find annotations. Consult the Javadoc for
`{AnnotationSupport}` for further details.
NOTE: The `isAnnotated()` methods do not find repeatable annotations. To check for repeatable annotations,
use one of the `findRepeatableAnnotations()` methods and verify that the returned list is not empty.
NOTE: See also: <>
[[classes]]
== Class Support
`ClassSupport` provides static utility methods for working with classes (i.e., instances
of `java.lang.Class`). Consult the Javadoc for `{ClassSupport}` for further details.
[[reflection]]
== Reflection Support
`ReflectionSupport` provides static utility methods that augment the standard JDK
reflection and class-loading mechanisms. These include methods to scan the classpath in
search of classes matching specified predicates, to load and create new instances of a
class, and to find and invoke methods. Some of these methods traverse class hierarchies
to locate matching methods. Consult the Javadoc for `{ReflectionSupport}` for further
details.
NOTE: See also: <>
[[modifier]]
== Modifier Support
`ModifierSupport` provides static utility methods for working with member and class
modifiers -- for example, to determine if a member is declared as `public`, `private`,
`abstract`, `static`, etc. Consult the Javadoc for `{ModifierSupport}` for further
details.
[[conversion]]
== Conversion Support
`ConversionSupport` (in the `org.junit.platform.commons.support.conversion` package)
provides support for converting from strings to primitive types and their corresponding
wrapper types, date and time types from the `java.time package`, and some additional
common Java types such as `File`, `BigDecimal`, `BigInteger`, `Currency`, `Locale`, `URI`,
`URL`, `UUID`, etc. Consult the Javadoc for `{ConversionSupport}` for further details.
[[search-semantics]]
== Field and Method Search Semantics
Various methods in `AnnotationSupport` and `ReflectionSupport` use search algorithms that
traverse type hierarchies to locate matching fields and methods – for example,
`AnnotationSupport.findAnnotatedFields(...)`, `ReflectionSupport.findMethods(...)`, etc.
The field and method search algorithms adhere to standard Java semantics regarding whether
a given field or method is visible or overridden according to the rules of the Java
language.
================================================
FILE: documentation/modules/ROOT/pages/extensions/test-instance-factories.adoc
================================================
= Test Instance Factories
`{TestInstanceFactory}` defines the API for `Extensions` that wish to _create_ test class
instances.
Common use cases include acquiring the test instance from a dependency injection
framework or invoking a static factory method to create the test class instance.
If no `TestInstanceFactory` is registered, the framework will invoke the _sole_
constructor for the test class to instantiate it, potentially resolving constructor
arguments via registered `ParameterResolver` extensions.
Extensions that implement `TestInstanceFactory` can be registered on test interfaces,
top-level test classes, or `@Nested` test classes.
[WARNING]
====
Registering multiple extensions that implement `TestInstanceFactory` for any single class
will result in an exception being thrown for all tests in that class, in any subclass,
and in any nested class. Note that any `TestInstanceFactory` registered in a superclass
or _enclosing_ class (i.e., in the case of a `@Nested` test class) is _inherited_. It is
the user's responsibility to ensure that only a single `TestInstanceFactory` is
registered for any specific test class.
====
[NOTE]
.Accessing the test-scoped `ExtensionContext`
====
You may override the `getTestInstantiationExtensionContextScope(...)` method to return
`TEST_METHOD` to make test-specific data available to your extension implementation or if
you want to xref:extensions/keeping-state-in-extensions.adoc[keep state] on the test method level.
====
================================================
FILE: documentation/modules/ROOT/pages/extensions/test-instance-post-processing.adoc
================================================
= Test Instance Post-processing
`{TestInstancePostProcessor}` defines the API for `Extensions` that wish to _post
process_ test instances.
Common use cases include injecting dependencies into the test instance, invoking custom
initialization methods on the test instance, etc.
For a concrete example, consult the source code for the `{MockitoExtension}` and the
`{SpringExtension}`.
[NOTE]
.Accessing the test-scoped `ExtensionContext`
====
You may override the `getTestInstantiationExtensionContextScope(...)` method to return
`TEST_METHOD` to make test-specific data available to your extension implementation or if
you want to xref:extensions/keeping-state-in-extensions.adoc[keep state] on the test method level.
====
================================================
FILE: documentation/modules/ROOT/pages/extensions/test-instance-pre-construct-callback.adoc
================================================
= Test Instance Pre-construct Callback
`{TestInstancePreConstructCallback}` defines the API for `Extensions` that wish to be invoked
_prior_ to test instances being constructed (by a constructor call or via
`{TestInstanceFactory}`).
This extension provides a symmetric call to `{TestInstancePreDestroyCallback}` and is useful
in combination with other extensions to prepare constructor parameters or keeping track of test
instances and their lifecycle.
[NOTE]
.Accessing the test-scoped `ExtensionContext`
====
You may override the `getTestInstantiationExtensionContextScope(...)` method to return
`TEST_METHOD` to make test-specific data available to your extension implementation or if
you want to xref:extensions/keeping-state-in-extensions.adoc[keep state] on the test method level.
====
================================================
FILE: documentation/modules/ROOT/pages/extensions/test-instance-pre-destroy-callback.adoc
================================================
= Test Instance Pre-destroy Callback
`{TestInstancePreDestroyCallback}` defines the API for `Extensions` that wish to process
test instances _after_ they have been used in tests and _before_ they are destroyed.
Common use cases include cleaning dependencies that have been injected into the
test instance, invoking custom de-initialization methods on the test instance, etc.
================================================
FILE: documentation/modules/ROOT/pages/extensions/test-lifecycle-callbacks.adoc
================================================
= Test Lifecycle Callbacks
The following interfaces define the APIs for extending tests at various points in the
test execution lifecycle. Consult the following sections for examples and the Javadoc for
each of these interfaces in the `{extension-api-package}` package for further details.
* `{BeforeAllCallback}`
** `{BeforeClassTemplateInvocationCallback}` (only applicable for
xref:writing-tests/class-templates.adoc[class templates])
*** `{BeforeEachCallback}`
**** `{BeforeTestExecutionCallback}`
**** `{AfterTestExecutionCallback}`
*** `{AfterEachCallback}`
** `{AfterClassTemplateInvocationCallback}` (only applicable for
xref:writing-tests/class-templates.adoc[class templates])
* `{AfterAllCallback}`
.Implementing Multiple Extension APIs
NOTE: Extension developers may choose to implement any number of these interfaces
within a single extension. Consult the source code of the `{SpringExtension}` for a
concrete example.
[[before-after-execution]]
== Before and After Test Execution Callbacks
`{BeforeTestExecutionCallback}` and `{AfterTestExecutionCallback}` define the APIs for
`Extensions` that wish to add behavior that will be executed _immediately before_ and
_immediately after_ a test method is executed, respectively. As such, these callbacks are
well suited for timing, tracing, and similar use cases. If you need to implement
callbacks that are invoked _around_ `@BeforeEach` and `@AfterEach` methods, implement
`BeforeEachCallback` and `AfterEachCallback` instead.
The following example shows how to use these callbacks to calculate and log the execution
time of a test method. `TimingExtension` implements both `BeforeTestExecutionCallback`
and `AfterTestExecutionCallback` in order to time and log the test execution.
[[timing-extension]]
.An extension that times and logs the execution of test methods
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/timing/TimingExtension.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/timing/TimingExtension.kt[tags=user_guide]
----
--
====
Since the `TimingExtensionTests` class registers the `TimingExtension` via `@ExtendWith`,
its tests will have this timing applied when they execute.
.A test class that uses the example TimingExtension
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/timing/TimingExtensionTests.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/timing/TimingExtensionTests.kt[tags=user_guide]
----
--
====
The following is an example of the logging produced when `TimingExtensionTests` is run.
....
INFO: Method [sleep20ms] took 24 ms.
INFO: Method [sleep50ms] took 53 ms.
....
================================================
FILE: documentation/modules/ROOT/pages/extensions/test-result-processing.adoc
================================================
= Test Result Processing
`{TestWatcher}` defines the API for extensions that wish to process the results of _test
method_ executions. Specifically, a `TestWatcher` will be invoked with contextual
information for the following events.
* `testDisabled`: invoked after a disabled _test method_ has been skipped
* `testSuccessful`: invoked after a _test method_ has completed successfully
* `testAborted`: invoked after a _test method_ has been aborted
* `testFailed`: invoked after a _test method_ has failed
NOTE: In contrast to the definition of "test method" presented in
xref:writing-tests/definitions.adoc[], in this context _test method_ refers to any `@Test` method
or `@TestTemplate` method (for example, a `@RepeatedTest` or `@ParameterizedTest`).
Extensions implementing this interface can be registered at the class level, instance
level, or method level. When registered at the class level, a `TestWatcher` will be
invoked for any contained _test method_ including those in `@Nested` classes. When
registered at the method level, a `TestWatcher` will only be invoked for the _test method_
for which it was registered.
[WARNING]
====
If a `TestWatcher` is registered via a non-static (instance) field – for example, using
`@RegisterExtension` – and the test class is configured with
`@TestInstance(Lifecycle.PER_METHOD)` semantics (which is the default lifecycle mode), the
`TestWatcher` will **not** be invoked with events for `@TestTemplate` methods (for
example, `@RepeatedTest` or `@ParameterizedTest`).
To ensure that a `TestWatcher` is invoked for all _test methods_ in a given class, it is
therefore recommended that the `TestWatcher` be registered at the class level with
`@ExtendWith` or via a `static` field with `@RegisterExtension` or `@ExtendWith`.
====
If there is a failure at the class level — for example, an exception thrown by a
`@BeforeAll` method — no test results will be reported. Similarly, if the test class is
disabled via an `ExecutionCondition` — for example, `@Disabled` — no test results will be
reported.
In contrast to other Extension APIs, a `TestWatcher` is not permitted to adversely
influence the execution of tests. Consequently, any exception thrown by a method in the
`TestWatcher` API will be logged at `WARNING` level and will not be allowed to propagate
or fail test execution.
[WARNING]
====
Any instances of `ExtensionContext.Store.CloseableResource` stored in the `Store` of the
provided `{ExtensionContext}` will be closed _before_ methods in the `TestWatcher` API are
invoked (see xref:extensions/keeping-state-in-extensions.adoc[]). You can use the parent context's `Store` to
work with such resources.
====
================================================
FILE: documentation/modules/ROOT/pages/migrating-from-junit4.adoc
================================================
= Migrating from JUnit 4
Although the JUnit Jupiter programming model and extension model do not support JUnit 4
features such as `Rules` and `Runners` natively, it is not expected that source code
maintainers will need to update all of their existing tests, test extensions, and custom
build test infrastructure to migrate to JUnit Jupiter.
Instead, JUnit provides a gentle migration path via a _JUnit Vintage test engine_ which
allows existing tests based on JUnit 3 and JUnit 4 to be executed using the JUnit Platform
infrastructure. Since all classes and annotations specific to JUnit Jupiter reside under
the `org.junit.jupiter` base package, having both JUnit 4 and JUnit Jupiter in the
classpath does not lead to any conflicts. It is therefore safe to maintain existing JUnit
4 tests alongside JUnit Jupiter tests and migrate them gradually.
[[running]]
== Running JUnit 4 Tests on the JUnit Platform
[WARNING]
====
The JUnit Vintage engine is deprecated and should only be used temporarily while
migrating tests to JUnit Jupiter or another testing framework with native JUnit Platform
support.
By default, if the JUnit Vintage engine is registered and discovers at least one test
class, it reports a xref:running-tests/discovery-issues.adoc[discovery issue] of INFO severity.
You can prevent this discovery issue from being reported by setting the
`junit.vintage.discovery.issue.reporting.enabled`
xref:running-tests/configuration-parameters.adoc[configuration parameter] to `false`.
====
Make sure that the `junit-vintage-engine` artifact is in your test runtime path. In that
case JUnit 3 and JUnit 4 tests will automatically be picked up by the JUnit Platform
launcher.
See the example projects in the {junit-examples-repo}[`junit-examples`] repository to
find out how this is done with Gradle and Maven.
[[categories-support]]
=== Categories Support
For test classes or methods that are annotated with `@Category`, the _JUnit Vintage test
engine_ exposes the category's fully qualified class name as a xref:running-tests/tags.adoc[tag]
for the corresponding test class or test method. For example, if a test method is
annotated with `@Category(Example.class)`, it will be tagged with `"com.acme.Example"`.
Similar to the `Categories` runner in JUnit 4, this information can be used to filter the
discovered tests before executing them (see xref:running-tests/intro.adoc[] for details).
[[parallel-execution]]
== Parallel Execution
The JUnit Vintage test engine supports parallel execution of top-level test classes and test methods,
allowing existing JUnit 3 and JUnit 4 tests to benefit from improved performance through
concurrent test execution. It can be enabled and configured using the following
xref:running-tests/configuration-parameters.adoc[configuration parameters]:
`junit.vintage.execution.parallel.enabled=true|false`::
Enable/disable parallel execution (defaults to `false`). Requires opt-in for `classes`
or `methods` to be executed in parallel using the configuration parameters below.
`junit.vintage.execution.parallel.classes=true|false`::
Enable/disable parallel execution of test classes (defaults to `false`).
`junit.vintage.execution.parallel.methods=true|false`::
Enable/disable parallel execution of test methods (defaults to `false`).
`junit.vintage.execution.parallel.pool-size=`::
Specifies the size of the thread pool to be used for parallel execution. By default, the
number of available processors is used.
[[parallel-execution-class-level]]
=== Parallelization at Class Level
Let's assume we have two test classes `FooTest` and `BarTest` with each class containing
three unit tests. Now, let's enable parallel execution of test classes:
[source,properties]
----
junit.vintage.execution.parallel.enabled=true
junit.vintage.execution.parallel.classes=true
----
With this setup, the `VintageTestEngine` will use two different threads,
one for each test class:
[source,plaintext]
----
ForkJoinPool-1-worker-1 - BarTest::test1
ForkJoinPool-1-worker-2 - FooTest::test1
ForkJoinPool-1-worker-1 - BarTest::test2
ForkJoinPool-1-worker-2 - FooTest::test2
ForkJoinPool-1-worker-1 - BarTest::test3
ForkJoinPool-1-worker-2 - FooTest::test3
----
[[parallel-execution-method-level]]
=== Parallelization at Method Level
Alternatively, we can enable parallel test execution at a method level,
rather than the class level:
[source,properties]
----
junit.vintage.execution.parallel.enabled=true
junit.vintage.execution.parallel.methods=true
----
Therefore, the test methods within each class will be executed in parallel, while
different test classes will be executed sequentially:
[source,plaintext]
----
ForkJoinPool-1-worker-1 - BarTest::test1
ForkJoinPool-1-worker-2 - BarTest::test2
ForkJoinPool-1-worker-3 - BarTest::test3
ForkJoinPool-1-worker-3 - FooTest::test1
ForkJoinPool-1-worker-2 - FooTest::test2
ForkJoinPool-1-worker-1 - FooTest::test3
----
[[parallel-execution-class-and-method-level]]
=== Full Parallelization
Finally, we can also enable parallelization at both class and method level:
[source,properties]
----
junit.vintage.execution.parallel.enabled=true
junit.vintage.execution.parallel.classes=true
junit.vintage.execution.parallel.methods=true
----
With these properties set, the `VintageTestEngine` will execute all tests classes and
methods in parallel, potentially significantly reducing the overall test suite execution time:
[source,plaintext]
----
ForkJoinPool-1-worker-6 - FooTest::test2
ForkJoinPool-1-worker-7 - BarTest::test3
ForkJoinPool-1-worker-3 - FooTest::test1
ForkJoinPool-1-worker-8 - FooTest::test3
ForkJoinPool-1-worker-5 - BarTest::test2
ForkJoinPool-1-worker-4 - BarTest::test1
----
[[parallel-execution-pool-size]]
=== Configuring the Pool Size
The default thread pool size is equal to the number of available processors. However, we
can also configure the pool size explicitly:
[source,properties]
----
junit.vintage.execution.parallel.enabled=true
junit.vintage.execution.parallel.classes=true
junit.vintage.execution.parallel.methods=true
junit.vintage.execution.parallel.pool-size=4
----
For instance, if we update our previous example that uses full parallelization and
configure the pool size to four, we can expect to see our six test methods executed with
a parallelism of four:
[source,plaintext]
----
ForkJoinPool-1-worker-2 - FooTest::test1
ForkJoinPool-1-worker-4 - BarTest::test2
ForkJoinPool-1-worker-3 - BarTest::test1
ForkJoinPool-1-worker-4 - BarTest::test3
ForkJoinPool-1-worker-2 - FooTest::test2
ForkJoinPool-1-worker-3 - FooTest::test3
----
As we can see, even though we set the thread pool size was four, only three threads were
used in this case. This happens because the pool adjusts the number of active threads
based on workload and system needs.
[[parallel-execution-disabled]]
=== Sequential Execution
On the other hand, if we disable parallel execution, the `VintageTestEngine`
will execute all tests sequentially, regardless of the other properties:
[source,properties]
----
junit.vintage.execution.parallel.enabled=false
junit.vintage.execution.parallel.classes=true
junit.vintage.execution.parallel.methods=true
----
Similarly, tests will be executed sequentially if you enable parallel execution in general
but enable neither class-level nor method-level parallelization.
[[tips]]
== Migration Tips
The following are topics that you should be aware of when migrating existing JUnit 4
tests to JUnit Jupiter.
* Annotations reside in the `org.junit.jupiter.api` package.
* Assertions reside in `org.junit.jupiter.api.Assertions`.
- Note that you may continue to use assertion methods from `org.junit.Assert` or any
other assertion library such as {AssertJ}, {Hamcrest}, {Truth}, etc.
* Assumptions reside in `org.junit.jupiter.api.Assumptions`.
- Note that JUnit Jupiter supports methods from JUnit 4's `org.junit.Assume` class for
assumptions. Specifically, JUnit Jupiter supports JUnit 4's
`AssumptionViolatedException` to signal that a test should be aborted instead of
marked as a failure.
* `@Before` and `@After` no longer exist; use `@BeforeEach` and `@AfterEach` instead.
* `@BeforeClass` and `@AfterClass` no longer exist; use `@BeforeAll` and `@AfterAll`
instead.
* `@Ignore` no longer exists: use `@Disabled` or one of the other built-in
xref:writing-tests/conditional-test-execution.adoc[execution conditions] instead
- See also <>.
* `@Category` no longer exists; use `@Tag` instead.
* `@RunWith` no longer exists; superseded by `@ExtendWith`.
- For `@RunWith(Enclosed.class)` use `@Nested`.
- For `@RunWith(Parameterized.class)` see <>.
* `@Rule` and `@ClassRule` no longer exist; superseded by `@ExtendWith` and
`@RegisterExtension`.
- See also <>.
* `@Test(expected = ...)` and the `ExpectedException` rule no longer exist; use
`Assertions.assertThrows(...)` instead.
- See <> if you still need to use
`ExpectedException`.
* Assertions and assumptions in JUnit Jupiter accept the failure message as their last
argument instead of the first one.
- See <> for details.
[[tips-parameterized]]
=== Parameterized test classes
Unless `@UseParametersRunnerFactory` is used, a JUnit 4 parameterized test class can be
converted into a JUnit Jupiter
xref:writing-tests/parameterized-classes-and-tests.adoc[`@ParameterizedClass`] by following these steps:
. Replace `@RunWith(Parameterized.class)` with `@ParameterizedClass`.
. Add a class-level `@MethodSource("methodName")` annotation where `methodName` is the
name of the method annotated with `@Parameters` and remove the `@Parameters` annotation
from the method.
. Replace `@BeforeParam` and `@AfterParam` with `@BeforeParameterizedClassInvocation` and
`@AfterParameterizedClassInvocation`, respectively, if there are any methods with such
annotations.
. Change the imports of the `@Test` and `@Parameter` annotations to use the
`org.junit.jupiter.params` package.
. Change assertions etc. to use the `org.junit.jupiter.api` package as usual.
. Optionally, remove all `public` modifiers from the class and its methods and fields.
====
[source,java,indent=0]
.Before
----
include::example$java/example/ParameterizedMigrationDemo.java[tags=before]
----
[source,java,indent=0]
.After
----
include::example$java/example/ParameterizedMigrationDemo.java[tags=after]
----
====
[[rule-support]]
== Limited JUnit 4 Rule Support
WARNING: _JUnit 4 rule support_ is deprecated for removal since version 6.0.0. Please
migrate to the corresponding APIs and extensions provided by JUnit Jupiter.
As stated above, JUnit Jupiter does not and will not support JUnit 4 rules natively. The
JUnit team realizes, however, that many organizations, especially large ones, are likely
to have large JUnit 4 code bases that make use of custom rules. To serve these
organizations and enable a gradual migration path the JUnit team has decided to support a
selection of JUnit 4 rules verbatim within JUnit Jupiter. This support is based on
adapters and is limited to those rules that are semantically compatible to the JUnit
Jupiter extension model, i.e. those that do not completely change the overall execution
flow of the test.
The `junit-jupiter-migrationsupport` module from JUnit Jupiter currently supports the
following three `Rule` types including subclasses of these types:
* `org.junit.rules.ExternalResource` (including `org.junit.rules.TemporaryFolder`)
* `org.junit.rules.Verifier` (including `org.junit.rules.ErrorCollector`)
* `org.junit.rules.ExpectedException`
As in JUnit 4, Rule-annotated fields as well as methods are supported. By using these
class-level extensions on a test class such `Rule` implementations in legacy code bases
can be _left unchanged_ including the JUnit 4 rule import statements.
This limited form of `Rule` support can be switched on by the class-level annotation
`{EnableRuleMigrationSupport}`. This annotation is a _composed annotation_ which enables
all rule migration support extensions: `VerifierSupport`, `ExternalResourceSupport`, and
`ExpectedExceptionSupport`. You may alternatively choose to annotate your test class with
`@EnableJUnit4MigrationSupport` which registers migration support for rules _and_ JUnit
4's `@Ignore` annotation (see <>).
However, if you intend to develop a new extension for JUnit Jupiter please use the new
extension model of JUnit Jupiter instead of the rule-based model of JUnit 4.
[[ignore-annotation-support]]
== JUnit 4 @Ignore Support
WARNING: _JUnit 4 `@Ignore` support_ is deprecated for removal since version 6.0.0. Please
use JUnit Jupiter's `@Disabled` annotation instead.
In order to provide a smooth migration path from JUnit 4 to JUnit Jupiter, the
`junit-jupiter-migrationsupport` module provides support for JUnit 4's `@Ignore`
annotation analogous to Jupiter's `{Disabled}` annotation.
To use `@Ignore` with JUnit Jupiter based tests, configure a _test_ dependency on the
`junit-jupiter-migrationsupport` module in your build and then annotate your test class
with `@ExtendWith(IgnoreCondition.class)` or `{EnableJUnit4MigrationSupport}` (which
automatically registers the `IgnoreCondition` along with
<>). The `IgnoreCondition` is an
`{ExecutionCondition}` that disables test classes or test methods that are annotated with
`@Ignore`.
[source,java,indent=0]
----
include::example$java/example/IgnoredTestsDemo.java[tags=user_guide]
----
[[failure-message-arguments]]
== Failure Message Arguments
The `Assumptions` and `Assertions` classes in JUnit Jupiter declare arguments in a
different order than in JUnit 4. In JUnit 4 assertion and assumption methods accept the
failure message as the first argument; whereas, in JUnit Jupiter assertion and assumption
methods accept the failure message as the last argument.
For instance, the method `assertEquals` in JUnit 4 is declared as `assertEquals(String
message, Object expected, Object actual)`, but in JUnit Jupiter it is declared as
`assertEquals(Object expected, Object actual, String message)`. The rationale for this is
that a failure message is _optional_, and optional arguments should be declared after
required arguments in a method signature.
The methods affected by this change are the following:
- Assertions
* `assertTrue`
* `assertFalse`
* `assertNull`
* `assertNotNull`
* `assertEquals`
* `assertNotEquals`
* `assertArrayEquals`
* `assertSame`
* `assertNotSame`
* `assertThrows`
- Assumptions
* `assumeTrue`
* `assumeFalse`
================================================
FILE: documentation/modules/ROOT/pages/overview.adoc
================================================
= Overview
:page-aliases: index.adoc, user-guide/index.adoc
The goal of this document is to provide comprehensive reference documentation for
programmers writing tests, extension authors, and engine authors as well as build tool
and IDE vendors.
[[what-is-junit]]
== What is JUnit?
JUnit is composed of several different modules from three different sub-projects.
[.text-center]
**JUnit {version} = _JUnit Platform_ + _JUnit Jupiter_ + _JUnit Vintage_**
The **JUnit Platform** serves as a foundation for
xref:advanced-topics/launcher-api.adoc[launching testing frameworks] on the JVM. It also
defines the `{TestEngine}` API for developing a testing framework that runs on the
platform. Furthermore, the platform provides a
xref:running-tests/console-launcher.adoc[Console Launcher] to launch the platform from the
command line and the xref:advanced-topics/junit-platform-suite-engine.adoc[] for running a
custom test suite using one or more test engines on the platform. First-class support for
the JUnit Platform also exists in popular IDEs (see
xref:running-tests/ide-support.adoc#intellij-idea[IntelliJ IDEA],
xref:running-tests/ide-support.adoc#eclipse[Eclipse],
xref:running-tests/ide-support.adoc#netbeans[NetBeans], and
xref:running-tests/ide-support.adoc#vscode[Visual Studio Code]) and build tools (see
xref:running-tests/build-support.adoc#gradle[Gradle],
xref:running-tests/build-support.adoc#maven[Maven],
xref:running-tests/build-support.adoc#ant[Ant],
xref:running-tests/build-support.adoc#bazel[Bazel], and
xref:running-tests/build-support.adoc#sbt[sbt]).
**JUnit Jupiter** is the combination of the xref:writing-tests/intro.adoc[programming model] and
xref:extensions/overview.adoc[extension model] for writing JUnit tests and extensions. The Jupiter
sub-project provides a `TestEngine` for running Jupiter based tests on the platform.
**JUnit Vintage** provides a `TestEngine` for running JUnit 3 and JUnit 4 based tests on
the platform. It requires JUnit 4.12 or later to be present on the class path or module
path. Note, however, that the JUnit Vintage engine is deprecated and should only be used
temporarily while migrating tests to JUnit Jupiter or another testing framework with
native JUnit Platform support.
[[java-versions]]
== Supported Java Versions
JUnit requires Java 17 (or higher) at runtime. However, you can still test code that
has been compiled with previous versions of the JDK.
[[getting-help]]
== Getting Help
Ask JUnit-related questions on {StackOverflow} or use the {DiscussionsQA}[Q&A category on GitHub Discussions].
[[getting-started]]
== Getting Started
[[getting-started-junit-artifacts]]
=== Downloading JUnit Artifacts
To find out what artifacts are available for download and inclusion in your project, refer
to xref:appendix.adoc#dependency-metadata[Dependency Metadata]. To set up dependency management for your build, refer to
xref:running-tests/build-support.adoc[] and the <>.
[[getting-started-features]]
=== JUnit Features
To find out what features are available in JUnit {version} and how to use them, read the
corresponding sections of this User Guide, organized by topic.
* xref:writing-tests/intro.adoc[Writing Tests in JUnit Jupiter]
* xref:migrating-from-junit4.adoc[Migrating from JUnit 4 to JUnit Jupiter]
* xref:running-tests/intro.adoc[]
* xref:extensions/overview.adoc[Extension Model for JUnit Jupiter]
* Advanced Topics
- xref:advanced-topics/launcher-api.adoc[]
- xref:advanced-topics/testkit.adoc[]
[[getting-started-example-projects]]
=== Example Projects
To see complete, working examples of projects that you can copy and experiment with, the
{junit-examples-repo}[`junit-examples`] repository is a good place to start. The
`junit-examples` repository hosts a collection of example projects based on JUnit Jupiter,
JUnit Vintage, and other testing frameworks. You'll find appropriate build scripts (e.g.,
`build.gradle`, `pom.xml`, etc.) in the example projects. The links below highlight some
of the combinations you can choose from.
* For Gradle and Java, check out the `{junit-jupiter-starter-gradle}` project.
* For Gradle and Kotlin, check out the `{junit-jupiter-starter-gradle-kotlin}` project.
* For Gradle and Groovy, check out the `{junit-jupiter-starter-gradle-groovy}` project.
* For Maven, check out the `{junit-jupiter-starter-maven}` project.
* For Ant, check out the `{junit-jupiter-starter-ant}` project.
* For Bazel, check out the `{junit-jupiter-starter-bazel}` project.
* For sbt, check out the `{junit-jupiter-starter-sbt}` project.
================================================
FILE: documentation/modules/ROOT/pages/release-notes.adoc
================================================
= Release Notes
:page-aliases: release-notes/index.adoc
//
//
This document contains the change log for all JUnit releases since 6.0 GA.
include::partial$release-notes/release-notes-6.1.0.adoc[]
include::partial$release-notes/release-notes-6.1.0-RC1.adoc[]
include::partial$release-notes/release-notes-6.1.0-M1.adoc[]
include::partial$release-notes/release-notes-6.0.3.adoc[]
include::partial$release-notes/release-notes-6.0.2.adoc[]
include::partial$release-notes/release-notes-6.0.1.adoc[]
include::partial$release-notes/release-notes-6.0.0.adoc[]
================================================
FILE: documentation/modules/ROOT/pages/running-tests/build-support.adoc
================================================
= Build Support
[[gradle]]
== Gradle
Starting with https://docs.gradle.org/4.6/release-notes.html[version 4.6], Gradle provides
https://docs.gradle.org/current/userguide/java_testing.html#using_junit5[native support]
for executing tests on the JUnit Platform. To enable it, you need to specify
`useJUnitPlatform()` within a `test` task declaration in `build.gradle`:
[source,groovy,indent=0]
[subs=attributes+]
----
test {
useJUnitPlatform()
}
----
Filtering by xref:running-tests/tags.adoc[tags],
xref:running-tests/tags.adoc#expressions[tag expressions], or engines is also supported:
[source,groovy,indent=0]
[subs=attributes+]
----
test {
useJUnitPlatform {
includeTags("fast", "smoke & feature-a")
// excludeTags("slow", "ci")
includeEngines("junit-jupiter")
// excludeEngines("junit-vintage")
}
}
----
Please refer to the
https://docs.gradle.org/current/userguide/java_testing.html[official Gradle documentation]
for a comprehensive list of options.
[[gradle-bom]]
=== Aligning dependency versions
TIP: See <> for details on how to override the version
of JUnit used in your Spring Boot application.
Unless you're using Spring Boot which defines its own way of managing dependencies, it is
recommended to use the JUnit Platform xref:appendix.adoc#dependency-metadata-junit-bom[Bill of Materials (BOM)] to align the
versions of all JUnit artifacts.
[source,groovy,indent=0]
[subs=attributes+]
.Explicit platform dependency on the BOM
----
dependencies {
testImplementation(platform("org.junit:junit-bom:{version}"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
----
Using the BOM allows you to omit the version when declaring dependencies on all artifacts
with the `org.junit.platform`, `org.junit.jupiter`, and `org.junit.vintage` group IDs.
Since all JUnit artifacts declare a
https://docs.gradle.org/current/userguide/platforms.html[platform] dependency on the BOM,
you usually don't need to declare an explicit dependency on it yourself. Instead, it's
sufficient to declare _one_ regular dependency that includes a version number. Gradle will
then pull in the BOM automatically so you can omit the version for all other JUnit
artifacts.
[source,groovy,indent=0]
[subs=attributes+]
.Implicit platform dependency on the BOM
----
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:{version}") // <1>
testRuntimeOnly("org.junit.platform:junit-platform-launcher") // <2>
}
----
<1> Dependency declaration with explicit version. Pulls in the `junit-bom` automatically.
<2> Dependency declaration without version. The version is supplied by the `junit-bom`.
[WARNING]
.Declaring a dependency on junit-platform-launcher
====
Even though pre-8.0 versions of Gradle don't require declaring an explicit
dependency on `junit-platform-launcher`, it is recommended to do so to ensure the versions
of JUnit artifacts on the test runtime classpath are aligned.
Moreover, doing so is recommended and in some cases even required when importing the
project into an IDE like xref:running-tests/ide-support.adoc#eclipse[Eclipse] or
xref:running-tests/ide-support.adoc#intellij-idea[IntelliJ IDEA].
====
[[gradle-engines-configure]]
=== Configuring Test Engines
In order to run any tests at all, a `TestEngine` implementation must be on the classpath.
To configure support for JUnit Jupiter based tests, configure a `testImplementation` dependency
on the dependency-aggregating JUnit Jupiter artifact similar to the following.
[source,groovy,indent=0]
[subs=attributes+]
----
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:{version}")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
----
Alternatively, you can use Gradle's
https://docs.gradle.org/current/userguide/jvm_test_suite_plugin.html[JVM Test Suite]
support.
[source,kotlin,indent=0]
[subs=attributes+]
.Kotlin DSL
----
testing {
suites {
named("test") {
useJUnitJupiter("{version}")
}
}
}
----
[source,groovy,indent=0]
[subs=attributes+]
.Groovy DSL
----
testing {
suites {
test {
useJUnitJupiter("{version}")
}
}
}
----
The JUnit Platform can run JUnit 4 based tests as long as you configure a `testImplementation`
dependency on JUnit 4 and a `testRuntimeOnly` dependency on the JUnit Vintage `TestEngine`
implementation similar to the following.
[source,groovy,indent=0]
[subs=attributes+]
----
dependencies {
testImplementation("junit:junit:{junit4-version}")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:{version}")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
----
[[gradle-config-params]]
=== Configuration Parameters
The standard Gradle `test` task currently does not provide a dedicated DSL to set JUnit
Platform xref:running-tests/configuration-parameters.adoc[configuration parameters] to influence test
discovery and execution. However, you can provide configuration parameters within the
build script via system properties (as shown below) or via the
`junit-platform.properties` file.
[source,groovy,indent=0]
----
test {
// ...
systemProperty("junit.jupiter.conditions.deactivate", "*")
systemProperty("junit.jupiter.extensions.autodetection.enabled", true)
systemProperty("junit.jupiter.testinstance.lifecycle.default", "per_class")
// ...
}
----
[[gradle-logging]]
=== Configuring Logging (optional)
JUnit uses the Java Logging APIs in the `java.util.logging` package (a.k.a. _JUL_) to
emit warnings and debug information. Please refer to the official documentation of
`{LogManager}` for configuration options.
Alternatively, it's possible to redirect log messages to other logging frameworks such as
{Log4j} or {Logback}. To use a logging framework that provides a custom implementation of
`{LogManager}`, set the `java.util.logging.manager` system property to the _fully
qualified class name_ of the `{LogManager}` implementation to use. The example below
demonstrates how to configure Log4j{nbsp}2.x (see {Log4j_JDK_Logging_Adapter} for
details).
[source,groovy,indent=0]
[subs=attributes+]
----
test {
systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager")
// Avoid overhead (see https://logging.apache.org/log4j/2.x/manual/jmx.html#enabling-jmx)
systemProperty("log4j2.disableJmx", "true")
}
----
Other logging frameworks provide different means to redirect messages logged using
`java.util.logging`. For example, for {Logback} you can use the
https://www.slf4j.org/legacy.html#jul-to-slf4j[JUL to SLF4J Bridge] by adding it as a
dependency to the test runtime classpath.
[[maven]]
== Maven
Maven Surefire and Maven Failsafe provide
https://maven.apache.org/surefire/maven-surefire-plugin/examples/junit-platform.html[native support]
for executing tests on the JUnit Platform. The `pom.xml` file in the
`{junit-jupiter-starter-maven}` project demonstrates how to use the Maven Surefire plugin
and can serve as a starting point for configuring your Maven build.
[WARNING]
.Minimum required version of Maven Surefire/Failsafe
====
As of JUnit 6.0, the minimum required version of Maven Surefire/Failsafe is 3.0.0.
====
[[maven-bom]]
=== Aligning dependency versions
Unless you're using Spring Boot which defines its own way of managing dependencies, it is
recommended to use the JUnit Platform xref:appendix.adoc#dependency-metadata-junit-bom[Bill of Materials (BOM)] to align the
versions of all JUnit artifacts.
[source,xml,indent=0]
[subs=attributes+]
----
org.junit
junit-bom
{version}
pom
import
----
Using the BOM allows you to omit the version when declaring dependencies on all artifacts
with the `org.junit.platform`, `org.junit.jupiter`, and `org.junit.vintage` group IDs.
TIP: See <> for details on how to override the version
of JUnit used in your Spring Boot application.
[[maven-engines-configure]]
=== Configuring Test Engines
In order to have Maven Surefire or Maven Failsafe run any tests at all, at least one
`TestEngine` implementation must be added to the test classpath.
To configure support for JUnit Jupiter based tests, configure `test` scoped dependencies
on the JUnit Jupiter API and the JUnit Jupiter `TestEngine` implementation similar to the
following.
[source,xml,indent=0]
[subs=attributes+]
----
org.junit.jupiter
junit-jupiter
{version}
test
maven-surefire-plugin
{surefire-version}
maven-failsafe-plugin
{surefire-version}
----
Maven Surefire and Maven Failsafe can run JUnit 4 based tests alongside Jupiter tests as
long as you configure `test` scoped dependencies on JUnit 4 and the JUnit Vintage
`TestEngine` implementation similar to the following.
[source,xml,indent=0]
[subs=attributes+]
----
junit
junit
{junit4-version}
test
org.junit.vintage
junit-vintage-engine
{version}
test
maven-surefire-plugin
{surefire-version}
maven-failsafe-plugin
{surefire-version}
----
[[maven-filter-test-class-names]]
=== Filtering by Test Class Names
The Maven Surefire Plugin will scan for test classes whose fully qualified names match
the following patterns.
- `+++**/Test*.java+++`
- `+++**/*Test.java+++`
- `+++**/*Tests.java+++`
- `+++**/*TestCase.java+++`
Moreover, it will exclude all nested classes (including static member classes) by default.
Note, however, that you can override this default behavior by configuring explicit
`include` and `exclude` rules in your `pom.xml` file. For example, to keep Maven Surefire
from excluding static member classes, you can override its exclude rules as follows.
[source,xml,indent=0]
[subs=attributes+]
.Overriding exclude rules of Maven Surefire
----
maven-surefire-plugin
{surefire-version}
----
Please see the
https://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html[Inclusions and Exclusions of Tests]
documentation for Maven Surefire for details.
[[maven-filter-tags]]
=== Filtering by Tags
You can filter tests by xref:running-tests/tags.adoc[tags] or
xref:running-tests/tags.adoc#expressions[tag expressions] using the following configuration
properties.
- to include _tags_ or _tag expressions_, use `groups`.
- to exclude _tags_ or _tag expressions_, use `excludedGroups`.
[source,xml,indent=0]
[subs=attributes+]
----
maven-surefire-plugin
{surefire-version}
acceptance | !feature-a
integration, regression
----
[[maven-config-params]]
=== Configuration Parameters
You can set JUnit Platform xref:running-tests/configuration-parameters.adoc[configuration parameters] to
influence test discovery and execution by declaring the `configurationParameters`
property and providing key-value pairs using the Java `Properties` file syntax (as shown
below) or via the `junit-platform.properties` file.
[source,xml,indent=0]
[subs=attributes+]
----
maven-surefire-plugin
{surefire-version}
junit.jupiter.conditions.deactivate = *
junit.jupiter.extensions.autodetection.enabled = true
junit.jupiter.testinstance.lifecycle.default = per_class
----
[[ant]]
== Ant
Starting with version `1.10.3`, link:https://ant.apache.org/[Ant] has a
link:https://ant.apache.org/manual/Tasks/junitlauncher.html[`junitlauncher`] task that
provides native support for launching tests on the JUnit Platform. The `junitlauncher`
task is solely responsible for launching the JUnit Platform and passing it the selected
collection of tests. The JUnit Platform then delegates to registered test engines to
discover and execute the tests.
The `junitlauncher` task attempts to align as closely as possible with native Ant
constructs such as
link:https://ant.apache.org/manual/Types/resources.html#collection[resource collections]
for allowing users to select the tests that they want executed by test engines. This gives
the task a consistent and natural feel when compared to many other core Ant tasks.
Starting with version `1.10.6` of Ant, the `junitlauncher` task supports
link:https://ant.apache.org/manual/Tasks/junitlauncher.html#fork[forking the tests in a separate JVM].
The `build.xml` file in the `{junit-jupiter-starter-ant}` project demonstrates how to use
the task and can serve as a starting point.
=== Basic Usage
The following example demonstrates how to configure the `junitlauncher` task to select a
single test class (i.e., `com.example.project.CalculatorTests`).
[source,xml,indent=0]
----
----
The `test` element allows you to specify a single test class that you want to be selected
and executed. The `classpath` element allows you to specify the classpath to be used to
launch the JUnit Platform. This classpath will also be used to locate test classes that
are part of the execution.
The following example demonstrates how to configure the `junitlauncher` task to select
test classes from multiple locations.
[source,xml,indent=0]
----
----
In the above example, the `testclasses` element allows you to select multiple test
classes that reside in different locations.
For further details on usage and configuration options please refer to the official Ant
documentation for the
link:https://ant.apache.org/manual/Tasks/junitlauncher.html[`junitlauncher` task].
[[bazel]]
== Bazel
For Bazel, add dependencies on
https://github.com/bazel-contrib/rules_jvm_external[rules_jvm_external] and
https://github.com/bazel-contrib/rules_jvm[contrib_rules_jvm] to your module's manifest
and install JUnit's artifacts.
[source,skylark]
[subs=attributes+]
.MODULE.bazel
----
bazel_dep(name = "rules_jvm_external", version = "6.9")
bazel_dep(name = "contrib_rules_jvm", version = "0.31.1")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
maven.install(
artifacts = [
"org.junit.jupiter:junit-jupiter-api:{version}",
"org.junit.jupiter:junit-jupiter-params:{version}",
"org.junit.jupiter:junit-jupiter-engine:{version}",
"org.junit.platform:junit-platform-launcher:{version}",
"org.junit.platform:junit-platform-reporting:{version}",
],
repositories = [
"https://repo1.maven.org/maven2",
],
)
use_repo(maven, "maven")
----
Next, in your `BUILD` file, define a `java_test_suite` for your tests.
[source,skylark]
[subs=attributes+]
.BUILD
----
load("@rules_jvm_external//:defs.bzl", "artifact")
load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite")
java_library(
name = "your-library-name",
srcs = glob(["src/main/java/**/*.java"]),
)
java_test_suite(
name = "your-library-name-tests",
size = "small",
srcs = glob(["src/test/java/**/*.java"]),
runner = "junit5",
test_suffixes = ["Test.java", "Tests.java"],
runtime_deps = runtime_deps = [
artifact("org.junit.jupiter:junit-jupiter-engine"),
artifact("org.junit.platform:junit-platform-launcher"),
artifact("org.junit.platform:junit-platform-reporting"),
],
deps = [
":your-library-name",
artifact("org.junit.jupiter:junit-jupiter-api"),
artifact("org.junit.jupiter:junit-jupiter-params"),
],
)
----
To execute your tests, run `bazel test //...`. For a full example, please check out the
`{junit-jupiter-starter-bazel}` example project.
[[sbt]]
== sbt
For sbt, you first need to declare a dependency on the
https://github.com/sbt/sbt-jupiter-interface[sbt-jupiter-interface] plugin. Despite its
name, it supports running any class-based JUnit Platform test engine.
[source,scala]
.project/plugins.sbt
----
addSbtPlugin("com.github.sbt.junit" % "sbt-jupiter-interface" % "0.17.0")
----
In your build definition, add dependencies on the corresponding `jupiter-interface` along
JUnit's artifacts. Moreover, you should configure the `jupiterTestFramework` and its
https://github.com/sbt/sbt-jupiter-interface#framework-options[options].
[source,scala]
[subs=attributes+]
.build.sbt
----
lazy val root = project
.in(file("."))
.settings(
name := "junit-jupiter-starter-sbt",
libraryDependencies ++= Seq(
"com.github.sbt.junit" % "jupiter-interface" % JupiterKeys.jupiterVersion.value % Test,
"org.junit.jupiter" % "junit-jupiter" % "{version}" % Test,
"org.junit.platform" % "junit-platform-launcher" % "{version}" % Test,
),
testOptions += Tests.Argument(jupiterTestFramework, "--display-mode=tree")
)
----
Finally, execute your tests by running `sbt test`. For a full example, please check out
the `{junit-jupiter-starter-sbt}` example project.
[[spring-boot]]
== Spring Boot
link:https://spring.io/projects/spring-boot[Spring Boot] provides automatic support for
managing the version of JUnit used in your project. In addition, the
`spring-boot-starter-test` artifact automatically includes testing libraries such as JUnit
Jupiter, AssertJ, Mockito, etc.
If your build relies on dependency management support from Spring Boot, you should not
import JUnit's xref:appendix.adoc#dependency-metadata-junit-bom[Bill of Materials (BOM)] in your build script since that would
result in duplicate (and potentially conflicting) management of JUnit dependencies.
If you need to override the version of a dependency used in your Spring Boot application,
you have to override the exact name of the
link:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.dependency-versions.properties[version property]
defined in the BOM used by the Spring Boot plugin. For example, the name of the JUnit
Jupiter version property in Spring Boot is `junit-jupiter.version`. The mechanism for
changing a dependency version is documented for both
link:https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#managing-dependencies.dependency-management-plugin.customizing[Gradle]
and
link:https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/#using.parent-pom[Maven].
With Gradle you can override the JUnit Jupiter version by including the following in your
`build.gradle` file.
[source,groovy,indent=0]
[subs=attributes+]
----
ext['junit-jupiter.version'] = '{version}'
----
With Maven you can override the JUnit Jupiter version by including the following in your
`pom.xml` file.
[source,xml,indent=0]
[subs=attributes+]
----
{version}
----
================================================
FILE: documentation/modules/ROOT/pages/running-tests/capturing-standard-output-error.adoc
================================================
= Capturing Standard Output/Error
The JUnit Platform provides opt-in support for capturing output printed to `System.out`
and `System.err`. To enable it, set the `junit.platform.output.capture.stdout` and/or
`junit.platform.output.capture.stderr` xref:running-tests/configuration-parameters.adoc[configuration
parameter] to `true`. In addition, you may configure the maximum number of buffered bytes
to be used per executed test or container using `junit.platform.output.capture.maxBuffer`.
If enabled, the JUnit Platform captures the corresponding output and publishes it as a
report entry using the `stdout` or `stderr` keys to all registered
`{TestExecutionListener}` instances immediately before reporting the test or container as
finished.
Please note that the captured output will only contain output emitted by the thread that
was used to execute a container or test. Any output by other threads will be omitted
because particularly when
xref:writing-tests/parallel-execution.adoc[executing tests in parallel] it would be impossible
to attribute it to a specific test or container.
================================================
FILE: documentation/modules/ROOT/pages/running-tests/configuration-parameters.adoc
================================================
= Configuration Parameters
In addition to instructing the platform which test classes and test engines to include,
which packages to scan, etc., it is sometimes necessary to provide additional custom
configuration parameters that are specific to a particular test engine, listener, or
registered extension. For example, the JUnit Jupiter `TestEngine` supports _configuration
parameters_ for the following use cases.
- xref:writing-tests/test-instance-lifecycle.adoc#default[Changing the Default Test Instance Lifecycle]
- xref:extensions/registering-extensions.adoc#registration-automatic-enabling[Enabling Automatic Extension Detection]
- xref:extensions/conditional-test-execution.adoc#deactivation[Deactivating Conditions]
- xref:writing-tests/display-names.adoc#generator-default[Setting the Default Display Name Generator]
_Configuration Parameters_ are text-based key-value pairs that can be supplied to test
engines running on the JUnit Platform via one of the following mechanisms.
1. The `configurationParameter()` and `configurationParameters()` methods in
`LauncherDiscoveryRequestBuilder` which
is used to build a request supplied to the xref:advanced-topics/launcher-api.adoc[Launcher API].
+
When running tests via one of the tools provided by the JUnit Platform you can specify
configuration parameters as follows:
* xref:running-tests/console-launcher.adoc[Console Launcher]: use the `--config` command-line
option.
* xref:running-tests/build-support.adoc#gradle-config-params[Gradle]: use the `systemProperty` or
`systemProperties` DSL.
* xref:running-tests/build-support.adoc#maven-config-params[Maven Surefire provider]: use the
`configurationParameters` property.
2. The `configurationParametersResources()` method in `LauncherDiscoveryRequestBuilder`.
+
When running tests via the xref:running-tests/console-launcher.adoc[Console Launcher] you can
specify custom configuration files using the `--config-resource` command-line option.
3. JVM system properties.
4. The JUnit Platform default configuration file: a file named `junit-platform.properties`
in the root of the class path that follows the syntax rules for Java `Properties`
files.
NOTE: Configuration parameters are looked up in the exact order defined above.
Consequently, configuration parameters supplied directly to the `Launcher` take
precedence over those supplied via custom configuration files, system properties, and the
default configuration file. Similarly, configuration parameters supplied via system
properties take precedence over those supplied via the default configuration file.
[[pattern]]
== Pattern Matching Syntax
This section describes the pattern matching syntax that is applied to the _configuration
parameters_ used for the following features.
- xref:extensions/conditional-test-execution.adoc#deactivation[Deactivating Conditions]
- xref:advanced-topics/launcher-api.adoc#listeners-custom-deactivation[Deactivating a TestExecutionListener]
- xref:running-tests/stack-trace-pruning.adoc[]
- xref:extensions/registering-extensions.adoc#registration-automatic-filtering[Filtering Auto-detected Extensions]
If the value for the given _configuration parameter_ consists solely of an asterisk
(`+++*+++`), the pattern will match against all candidate classes. Otherwise, the value
will be treated as a comma-separated list of patterns where each pattern will be matched
against the fully qualified class name (_FQCN_) of each candidate class. Any dot (`.`) in
a pattern will match against a dot (`.`) or a dollar sign (`$`) in a FQCN. Any asterisk
(`+++*+++`) will match against one or more characters in a FQCN. All other characters in a
pattern will be matched one-to-one against a FQCN.
Examples:
- `+++*+++`: matches all candidate classes.
- `+++org.junit.*+++`: matches all candidate classes under the `org.junit` base package and
any of its subpackages.
- `+++*.MyCustomImpl+++`: matches every candidate class whose simple class name is exactly
`MyCustomImpl`.
- `+++*System*+++`: matches every candidate class whose FQCN contains `System`.
- `+++*System*+++, +++*Unit*+++`: matches every candidate class whose FQCN contains
`System` or `Unit`.
- `org.example.MyCustomImpl`: matches the candidate class whose FQCN is exactly
`org.example.MyCustomImpl`.
- `org.example.MyCustomImpl, org.example.TheirCustomImpl`: matches candidate classes whose
FQCN is exactly `org.example.MyCustomImpl` or `org.example.TheirCustomImpl`.
================================================
FILE: documentation/modules/ROOT/pages/running-tests/console-launcher.adoc
================================================
= Console Launcher
The `{ConsoleLauncher}` is a command-line Java application that lets you launch the JUnit
Platform from the console. For example, it can be used to run JUnit Vintage and JUnit
Jupiter tests and print test execution results to the console.
An executable _Fat JAR_ (`junit-platform-console-standalone-{version}.jar`) that
contains the contents of all of its dependencies is published in the {Maven_Central}
repository under the
https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone[junit-platform-console-standalone]
directory. It contains the contents of the following artifacts:
include::partial$console-launcher-standalone-shadowed-artifacts.adoc[]
[NOTE]
====
Since the `junit-platform-console-standalone` JAR contains the contents of all of its
dependencies, its Maven POM does not declare any dependencies.
Furthermore, it is not very likely that you would need to include a dependency on the
`junit-platform-console-standalone` artifact in your project's Maven POM or Gradle build
script. On the contrary, the executable `junit-platform-console-standalone` JAR is
typically invoked directly from the command line or a shell script without a build script.
If you need to declare dependencies in your build script on some of the artifacts
contained in the `junit-platform-console-standalone` artifact, you should declare
dependencies only on the JUnit artifacts that are used in your project. To simplify
dependency management of JUnit artifacts in your build, you may wish to use the
`junit-jupiter` aggregator artifact or `junit-bom`. See xref:appendix.adoc#dependency-metadata[Dependency Metadata] for
details.
====
You can https://docs.oracle.com/javase/tutorial/deployment/jar/run.html[run] the
standalone `ConsoleLauncher` as shown below.
[source,console,subs=attributes+]
----
$ java -jar junit-platform-console-standalone-{version}.jar execute
├─ JUnit Vintage
│ └─ example.JUnit4Tests
│ └─ standardJUnit4Test ✔
└─ JUnit Jupiter
├─ StandardTests
│ ├─ succeedingTest() ✔
│ └─ skippedTest() ↷ for demonstration purposes
└─ A special test case
├─ Custom test name containing spaces ✔
├─ ╯°□°)╯ ✔
└─ 😱 ✔
Test run finished after 64 ms
[ 5 containers found ]
[ 0 containers skipped ]
[ 5 containers started ]
[ 0 containers aborted ]
[ 5 containers successful ]
[ 0 containers failed ]
[ 6 tests found ]
[ 1 tests skipped ]
[ 5 tests started ]
[ 0 tests aborted ]
[ 5 tests successful ]
[ 0 tests failed ]
----
You can also run the standalone `ConsoleLauncher` as shown below (for example, to include
all jars in a directory):
[source,console,subs=attributes+]
----
$ java -cp classes:testlib/* org.junit.platform.console.ConsoleLauncher
----
[[options]]
== Subcommands and Options
The `{ConsoleLauncher}` provides the following subcommands:
----
include::partial$console-launcher-options.txt[]
----
[[options-discovering-tests]]
=== Discovering tests
----
include::partial$console-launcher-discover-options.txt[]
----
[[options-executing-tests]]
=== Executing tests
.Exit Code
NOTE: On successful runs, the `{ConsoleLauncher}` exits with a status code of `0`.
All non-zero codes indicate an error of some sort. For example, status code `1`
is returned if any containers or tests failed. If no tests are discovered and the
`--fail-if-no-tests` command-line option is supplied, the `ConsoleLauncher` exits
with a status code of `2`. Unexpected or invalid user input yields a status code
of `3`. An exit code of `-1` indicates an unspecified error condition.
----
include::partial$console-launcher-execute-options.txt[]
----
[[options-listing-test-engines]]
=== Listing test engines
----
include::partial$console-launcher-engines-options.txt[]
----
[[argument-files]]
== Argument Files (@-files)
On some platforms you may run into system limitations on the length of a command line when
creating a command line with lots of options or with long arguments.
The `ConsoleLauncher` supports _argument files_, also known as _@-files_. Argument files
are files that themselves contain arguments to be passed to the command. When the
underlying https://github.com/remkop/picocli[picocli] command line parser encounters an
argument beginning with the character `@`, it expands the contents of that file into the
argument list.
The arguments within a file can be separated by spaces or newlines. If an argument
contains embedded whitespace, the whole argument should be wrapped in double or single
quotes -- for example, `"-f=My Files/Stuff.java"`.
If the argument file does not exist or cannot be read, the argument will be treated
literally and will not be removed. This will likely result in an "unmatched argument"
error message. You can troubleshoot such errors by executing the command with the
`picocli.trace` system property set to `DEBUG`.
Multiple _@-files_ may be specified on the command line. The specified path may be
relative to the current directory or absolute.
You can pass a real parameter with an initial `@` character by escaping it with an
additional `@` symbol. For example, `@@somearg` will become `@somearg` and will not be
subject to expansion.
[[redirecting-stdout-and-stderr]]
== Redirecting Standard Output/Error to Files
You can redirect the `System.out` (stdout) and `System.err` (stderr) output streams to
files using the `--redirect-stdout` and `--redirect-stderr` options:
[source,console,subs=attributes+]
----
$ java -jar junit-platform-console-standalone-{version}.jar \
--redirect-stdout=stdout.txt \
--redirect-stderr=stderr.txt
----
[NOTE]
====
If the `--redirect-stdout` and `--redirect-stderr` arguments point to the same file, both
output streams will be redirected to that file.
The default charset is used for writing to the files.
====
[[color-customization]]
== Color Customization
The colors used in the output of the `{ConsoleLauncher}` can be customized.
The option `--single-color` will apply a built-in monochrome style, while
`--color-palette` will accept a properties file to override the
https://en.wikipedia.org/wiki/ANSI_escape_code#Colors[ANSI SGR] color styling.
The properties file below demonstrates the default style:
[source,properties,indent=0]
----
SUCCESSFUL = 32
ABORTED = 33
FAILED = 31
SKIPPED = 35
CONTAINER = 35
TEST = 34
DYNAMIC = 35
REPORTED = 37
----
================================================
FILE: documentation/modules/ROOT/pages/running-tests/discovery-issues.adoc
================================================
= Discovery Issues
Test engines may encounter issues during test discovery. For example, the declaration of a
test class or method may be invalid. To avoid such issues from going unnoticed, the JUnit
Platform provides a xref:advanced-topics/engines.adoc#discovery-issues[mechanism for test engines] to
report them with different severity levels:
INFO::
Indicates that the engine encountered something that could be potentially problematic, but
could also happen due to a valid setup or configuration.
WARNING::
Indicates that the engine encountered something that is problematic and might lead to
unexpected behavior or will be removed or changed in a future release.
ERROR::
Indicates that the engine encountered something that is definitely problematic and will
lead to unexpected behavior.
If an engine reports an issue with a severity equal to or higher than a configurable
_critical_ severity, its tests will not be executed. Instead, the engine will be reported
as failed during execution with a `{DiscoveryIssueException}` listing all critical issues.
Non-critical issues will be logged but will not prevent the engine from executing its
tests. The `junit.platform.discovery.issue.severity.critical`
xref:running-tests/configuration-parameters.adoc[configuration parameter] can be used to set the critical
severity level. Currently, the default value is `ERROR` but it may be changed in a future
release.
TIP: To surface all discovery issues in your project, it is recommended to set the
`junit.platform.discovery.issue.severity.critical` configuration parameter to `INFO`.
In addition, registered `{LauncherDiscoveryListener}` implementations can receive
discovery issues via the `issueEncountered()` method. This allows IDEs and build tools to
report issues to the user in a more user-friendly way. For example, IDEs may choose to
display all issues in a list or table.
================================================
FILE: documentation/modules/ROOT/pages/running-tests/discovery-selectors.adoc
================================================
= Discovery Selectors
The JUnit Platform provides a rich set of discovery selectors that can be used to specify
which tests should be discovered or executed.
Discovery selectors can be created programmatically using the factory methods in the
`{DiscoverySelectors}` class, specified declaratively via annotations when using the
xref:advanced-topics/junit-platform-suite-engine.adoc[], via options of the xref:running-tests/console-launcher.adoc[], or
generically as strings via their identifiers.
The following discovery selectors are provided out of the box:
|===
| Java Type | API | Annotation | Console Launcher | Identifier
| `{ClasspathResourceSelector}` | `{DiscoverySelectors_selectClasspathResource}` | `{SelectClasspathResource}` | `--select-resource /foo.csv` | `resource:/foo.csv`
| `{ClasspathRootSelector}` | `{DiscoverySelectors_selectClasspathRoots}` | -- | `--scan-classpath bin` | `classpath-root:bin`
| `{ClassSelector}` | `{DiscoverySelectors_selectClass}` | `{SelectClasses}` | `--select-class com.acme.Foo` | `class:com.acme.Foo`
| `{DirectorySelector}` | `{DiscoverySelectors_selectDirectory}` | `{SelectDirectories}` | `--select-directory foo/bar` | `directory:foo/bar`
| `{FileSelector}` | `{DiscoverySelectors_selectFile}` | `{SelectFile}` | `--select-file dir/foo.txt` | `file:dir/foo.txt`
| `{IterationSelector}` | `{DiscoverySelectors_selectIteration}` | `{Select}("")` | `--select-iteration method=com.acme.Foo#m[1..2]` | `iteration:method:com.acme.Foo#m[1..2]`
| `{MethodSelector}` | `{DiscoverySelectors_selectMethod}` | `{SelectMethod}` | `--select-method com.acme.Foo#m` | `method:com.acme.Foo#m`
| `{ModuleSelector}` | `{DiscoverySelectors_selectModule}` | `{SelectModules}` | `--select-module com.acme` | `module:com.acme`
| `{NestedClassSelector}` | `{DiscoverySelectors_selectNestedClass}` | `{Select}("")` | `--select ` | `nested-class:com.acme.Foo/Bar`
| `{NestedMethodSelector}` | `{DiscoverySelectors_selectNestedMethod}` | `{Select}("")` | `--select ` | `nested-method:com.acme.Foo/Bar#m`
| `{PackageSelector}` | `{DiscoverySelectors_selectPackage}` | `{SelectPackages}` | `--select-package com.acme.foo` | `package:com.acme.foo`
| `{UniqueIdSelector}` | `{DiscoverySelectors_selectUniqueId}` | `{Select}("")` | `--select-unique-id ` | `uid:[engine:Foo]/[segment:Bar]`
| `{UriSelector}` | `{DiscoverySelectors_selectUri}` | `{SelectUris}` | `--select-uri \file:///foo.txt` | `uri:file:///foo.txt`
|===
================================================
FILE: documentation/modules/ROOT/pages/running-tests/ide-support.adoc
================================================
= IDE Support
[[intellij-idea]]
== IntelliJ IDEA
IntelliJ IDEA supports running tests on the JUnit Platform since version 2016.2. For more
information, please consult this https://jb.gg/junit-idea/[IntelliJ IDEA resource]. Note,
however, that it is recommended to use IDEA 2017.3 or newer since more recent versions of
IDEA download the following JARs automatically based on the API version used in the
project: `junit-platform-launcher`, `junit-jupiter-engine`, and `junit-vintage-engine`.
In order to use a different JUnit version (e.g., {version}), you may need to
include the corresponding versions of the `junit-platform-launcher`,
`junit-jupiter-engine`, and `junit-vintage-engine` JARs in the classpath.
.Additional Gradle Dependencies
[source,groovy]
[subs=attributes+]
----
testImplementation(platform("org.junit:junit-bom:{version}"))
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
----
.Additional Maven Dependencies
[source,xml]
[subs=attributes+]
----
org.junit.platform
junit-platform-launcher
test
org.junit.jupiter
junit-jupiter-engine
test
org.junit.vintage
junit-vintage-engine
test
org.junit
junit-bom
{version}
pom
import
----
[[eclipse]]
== Eclipse
Eclipse IDE offers support for the JUnit Platform since the Eclipse Oxygen.1a (4.7.1a)
release.
For more information on using JUnit Platform in Eclipse consult the official _Eclipse
support for JUnit 5_ section of the
https://www.eclipse.org/eclipse/news/4.7.1a/#junit-5-support[Eclipse Project Oxygen.1a
(4.7.1a) - New and Noteworthy] documentation.
[[netbeans]]
== NetBeans
NetBeans offers support for JUnit Jupiter and the JUnit Platform since the
https://netbeans.apache.org/download/nb100/nb100.html[Apache NetBeans 10.0 release].
For more information consult the JUnit 5 section of the
https://netbeans.apache.org/download/nb100/index.html#_junit_5[Apache NetBeans 10.0
release notes].
[[vscode]]
== Visual Studio Code
https://code.visualstudio.com/[Visual Studio Code] supports JUnit Jupiter and the JUnit
Platform via the
https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-test[Java Test
Runner] extension which is installed by default as part of the
https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack[Java
Extension Pack].
For more information consult the _Testing_ section of the
https://code.visualstudio.com/docs/languages/java#_testing[Java in Visual Studio Code]
documentation.
[[other]]
== Other IDEs
If you are using an editor or IDE other than one of those listed in the previous sections,
and it doesn't support running tests on the JUnit Platform, you can use the
xref:running-tests/console-launcher.adoc[] to run them from the command line.
================================================
FILE: documentation/modules/ROOT/pages/running-tests/intro.adoc
================================================
= Running Tests
This section explains how to run tests from IDEs and build tools.
================================================
FILE: documentation/modules/ROOT/pages/running-tests/source-launcher.adoc
================================================
= Source Launcher
Starting with Java 25 it is possible to write minimal source code test programs
using the `org.junit.start` module.
For example, create a `HelloTests.java` file with the following content:
[source,java]
----
import module org.junit.start;
void main() {
JUnit.run();
}
@Test
void stringLength() {
Assertions.assertEquals(11, "Hello JUnit".length());
}
----
With all required modular JAR files available in a local `lib/` directory, the
following Java 25+ command will discover and execute tests using the JUnit Platform.
It will also print the result tree to the console.
[source,shell]
----
java --module-path lib --add-modules org.junit.start HelloTests.java
╷
└─ JUnit Jupiter ✔
└─ HelloTests ✔
└─ stringLength() ✔
----
For details, please refer to the Javadoc of the `{JUnit}` class.
================================================
FILE: documentation/modules/ROOT/pages/running-tests/stack-trace-pruning.adoc
================================================
= Stack Trace Pruning
The JUnit Platform provides built-in support for pruning stack traces produced by failing
tests. This feature is enabled by default but can be disabled by setting the
`junit.platform.stacktrace.pruning.enabled` _configuration parameter_ to `false`.
When enabled, all calls from the `org.junit`, `jdk.internal.reflect`, and `sun.reflect`
packages are removed from the stack trace, unless the calls occur after the test itself
or any of its ancestors. For that reason, calls to `{Assertions}` or `{Assumptions}` will
never be excluded.
In addition, all elements prior to and including the first call from the JUnit Platform
`Launcher` will be removed.
================================================
FILE: documentation/modules/ROOT/pages/running-tests/tags.adoc
================================================
= Tags
Tags are a JUnit Platform concept for marking and filtering tests. The programming model
for adding tags to containers and tests is defined by the testing framework. For example,
in JUnit Jupiter based tests, the `@Tag` annotation (see
xref:writing-tests/tagging-and-filtering.adoc[]) should be used. For JUnit 4 based tests, the
Vintage engine maps `@Category` annotations to tags (see
xref:migrating-from-junit4.adoc#categories-support[Categories Support]). Other testing frameworks may define their
own annotation or other means for users to specify tags.
[[syntax-rules]]
== Syntax Rules for Tags
Regardless how a tag is specified, the JUnit Platform enforces the following rules:
* A tag must not be `null` or _blank_.
* A _stripped_ tag must not contain whitespace.
* A _stripped_ tag must not contain ISO control characters.
* A _stripped_ tag must not contain any of the following _reserved characters_.
- `,`: _comma_
- `(`: _left parenthesis_
- `)`: _right parenthesis_
- `&`: _ampersand_
- `|`: _vertical bar_
- `!`: _exclamation point_
NOTE: In the above context, "stripped" means that leading and trailing whitespace
characters have been removed using `java.lang.String.strip()`.
[[expressions]]
== Tag Expressions
Tag expressions are boolean expressions with the operators `!`, `&` and `|`. In addition,
`(` and `)` can be used to adjust for operator precedence.
Two special expressions are supported, `any()` and `none()`, which select all tests _with_
any tags at all, and all tests _without_ any tags, respectively.
These special expressions may be combined with other expressions just like normal tags.
.Operators (in descending order of precedence)
|===
| Operator | Meaning | Associativity
| `!` | not | right
| `&` | and | left
| `\|` | or | left
|===
If you are tagging your tests across multiple dimensions, tag expressions help you to
select which tests to execute. When tagging by test type (e.g., _micro_, _integration_,
_end-to-end_) and feature (e.g., *product*, *catalog*, *shipping*), the following tag
expressions can be useful.
[%header,cols="40,60"]
|===
| Tag Expression
| Selection
| `+++product+++`
| all tests for *product*
| `+++catalog \| shipping+++`
| all tests for *catalog* plus all tests for *shipping*
| `+++catalog & shipping+++`
| all tests for the intersection between *catalog* and *shipping*
| `+++product & !end-to-end+++`
| all tests for *product*, but not the _end-to-end_ tests
| `+++(micro \| integration) & (product \| shipping)+++`
| all _micro_ or _integration_ tests for *product* or *shipping*
|===
================================================
FILE: documentation/modules/ROOT/pages/running-tests/using-listeners-and-interceptors.adoc
================================================
= Using Listeners and Interceptors
The JUnit Platform provides the following listener APIs that allow JUnit, third parties,
and custom user code to react to events fired at various points during the discovery and
execution of a `TestPlan`.
* `{LauncherSessionListener}`: receives events when a `{LauncherSession}` is opened and
closed.
* `{LauncherInterceptor}`: intercepts test discovery and execution in the context of a
`LauncherSession`.
* `{LauncherDiscoveryListener}`: receives events that occur during test discovery.
* `{TestExecutionListener}`: receives events that occur during test execution.
The `LauncherSessionListener` API is typically implemented by build tools or IDEs and
registered automatically for you in order to support some feature of the build tool or IDE.
The `LauncherDiscoveryListener` and `TestExecutionListener` APIs are often implemented in
order to produce some form of report or to display a graphical representation of the test
plan in an IDE. Such listeners may be implemented and automatically registered by a build
tool or IDE, or they may be included in a third-party library – potentially registered
for you automatically. You can also implement and register your own listeners.
For details on registering and configuring listeners, see the following sections of this
guide.
* xref:advanced-topics/launcher-api.adoc#launcher-session-listeners-custom[Registering a LauncherSessionListener]
* xref:advanced-topics/launcher-api.adoc#launcher-interceptors-custom[Registering a LauncherInterceptor]
* xref:advanced-topics/launcher-api.adoc#launcher-discovery-listeners-custom[Registering a LauncherDiscoveryListener]
* xref:advanced-topics/launcher-api.adoc#listeners-custom[Registering a TestExecutionListener]
* xref:advanced-topics/launcher-api.adoc#listeners-config[Configuring a TestExecutionListener]
* xref:advanced-topics/launcher-api.adoc#listeners-custom-deactivation[Deactivating a TestExecutionListener]
The JUnit Platform provides the following listeners which you may wish to use with your
test suite.
xref:advanced-topics/junit-platform-reporting.adoc[] ::
`{LegacyXmlReportGeneratingListener}` can be used via the
xref:running-tests/console-launcher.adoc[] or registered manually to generate XML reports
compatible with the de facto standard for JUnit 4 based test reports.
+
`{OpenTestReportGeneratingListener}` generates an XML report in the event-based format
specified by {OpenTestReporting}. It is auto-registered and can be enabled and
configured via xref:running-tests/configuration-parameters.adoc[].
+
See xref:advanced-topics/junit-platform-reporting.adoc[] for details.
<> ::
`FlightRecordingExecutionListener` and `FlightRecordingDiscoveryListener` that generate
Java Flight Recorder events during test discovery and execution.
`{LoggingListener}` ::
`TestExecutionListener` for logging informational messages for all events via a
`BiConsumer` that consumes `Throwable` and `Supplier`.
`{SummaryGeneratingListener}` ::
`TestExecutionListener` that generates a summary of the test execution which can be
printed via a `PrintWriter`.
`{UniqueIdTrackingListener}` ::
`TestExecutionListener` that that tracks the unique IDs of all tests that were skipped
or executed during the execution of the `TestPlan` and generates a file containing the
unique IDs once execution of the `TestPlan` has finished.
[[recorder]]
== Flight Recorder Support
The JUnit Platform provides opt-in support for generating Flight Recorder events.
https://openjdk.java.net/jeps/328[JEP 328] describes the Java Flight Recorder (JFR) as
follows.
> Flight Recorder records events originating from applications, the JVM, and the OS.
Events are stored in a single file that can be attached to bug reports and examined by
support engineers, allowing after-the-fact analysis of issues in the period leading up
to a problem.
In order to record Flight Recorder events generated while running tests, you need to
start flight recording when launching a test suite via the following java command line
option.
-XX:StartFlightRecording:filename=...
Please consult the manual of your build tool for the appropriate commands.
To analyze the recorded events, use the
https://docs.oracle.com/en/java/javase/17/docs/specs/man/jfr.html[jfr]
command line tool shipped with recent JDKs or open the recording file with
https://jdk.java.net/jmc/[JDK Mission Control].
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/annotations.adoc
================================================
= Annotations
JUnit Jupiter supports the following annotations for configuring tests and extending the
framework.
Unless otherwise stated, all core annotations are located in the `{api-package}` package
in the `junit-jupiter-api` module.
`*@Test*`:: Denotes that a method is a test method. Unlike JUnit 4's `@Test` annotation,
this annotation does not declare any attributes, since test extensions in JUnit Jupiter
operate based on their own dedicated annotations. Such methods are inherited unless they
are overridden.
`*@ParameterizedTest*`:: Denotes that a method is a
xref:writing-tests/parameterized-classes-and-tests.adoc[parameterized test]. Such methods are inherited
unless they are overridden.
`*@RepeatedTest*`:: Denotes that a method is a test template for a
xref:writing-tests/repeated-tests.adoc[repeated test]. Such methods are inherited unless they
are overridden.
`*@TestFactory*`:: Denotes that a method is a test factory for
xref:writing-tests/dynamic-tests.adoc[dynamic tests]. Such methods are inherited unless they are
overridden.
`*@TestTemplate*`:: Denotes that a method is a
xref:writing-tests/test-templates.adoc[template for a test case] designed to be invoked multiple
times depending on the number of invocation contexts returned by the registered
xref:extensions/providing-invocation-contexts-for-test-templates.adoc[providers]. Such methods are inherited unless they are
overridden.
`*@TestClassOrder*`:: Used to configure the
xref:writing-tests/test-execution-order.adoc#classes[test class execution order] for `@Nested`
test classes in the annotated test class. Such annotations are inherited.
`*@TestMethodOrder*`:: Used to configure the
xref:writing-tests/test-execution-order.adoc#methods[test method execution order] for the
annotated test class; similar to JUnit 4's `@FixMethodOrder`. Such annotations are
inherited.
`*@TestInstance*`:: Used to configure the
xref:writing-tests/test-instance-lifecycle.adoc[test instance lifecycle] for the annotated test
class. Such annotations are inherited.
`*@DisplayName*`:: Declares a custom xref:writing-tests/display-names.adoc[display name] for the
test class or test method. Such annotations are not inherited.
`*@DisplayNameGeneration*`:: Declares a custom
xref:writing-tests/display-names.adoc#generator[display name generator] for the test class. Such
annotations are inherited.
`*@BeforeEach*`:: Denotes that the annotated method should be executed _before_ *each*
`@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current
class; analogous to JUnit 4's `@Before`. Such methods are inherited unless they are
overridden.
`*@AfterEach*`:: Denotes that the annotated method should be executed _after_ *each*
`@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current
class; analogous to JUnit 4's `@After`. Such methods are inherited unless they are
overridden.
`*@BeforeAll*`:: Denotes that the annotated method should be executed _before_ *all*
`@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current
top-level or `@Nested` test class; analogous to JUnit 4's `@BeforeClass`. Such methods are
inherited unless they are overridden and must be `static` unless the "per-class"
xref:writing-tests/test-instance-lifecycle.adoc[test instance lifecycle] is used.
`*@AfterAll*`:: Denotes that the annotated method should be executed _after_ *all*
`@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current
top-level or `@Nested` test class; analogous to JUnit 4's `@AfterClass`. Such methods are
inherited unless they are overridden and must be `static` unless the "per-class"
xref:writing-tests/test-instance-lifecycle.adoc[test instance lifecycle] is used.
`*@ParameterizedClass*`:: Denotes that the annotated class is a
xref:writing-tests/parameterized-classes-and-tests.adoc[parameterized class]. Such annotations are
inherited.
`*@BeforeParameterizedClassInvocation*`:: Denotes that the annotated method should be
executed once _before_ each invocation of a
xref:writing-tests/parameterized-classes-and-tests.adoc[parameterized class]. Such methods are inherited
unless they are overridden.
`*@AfterParameterizedClassInvocation*`:: Denotes that the annotated method should be
executed once _after_ each invocation of a
xref:writing-tests/parameterized-classes-and-tests.adoc[parameterized class]. Such methods are inherited
unless they are overridden.
`*@ClassTemplate*`:: Denotes that the annotated class is a
xref:writing-tests/class-templates.adoc[template for a test class] designed to be executed
multiple times depending on the number of invocation contexts returned by the registered
xref:extensions/providing-invocation-contexts-for-class-templates.adoc[providers]. Such annotations are inherited.
`*@Nested*`:: Denotes that the annotated class is a non-static
xref:writing-tests/nested-tests.adoc[nested test class]. Such annotations are not inherited.
`*@Tag*`:: Used to declare
xref:writing-tests/tagging-and-filtering.adoc[tags for filtering tests], either at the class or
method level; analogous to test groups in TestNG or Categories in JUnit 4. Such
annotations are inherited at the class level but not at the method level.
`*@Disabled*`:: Used to xref:writing-tests/disabling-tests.adoc[disable] a test class or test method;
analogous to JUnit 4's `@Ignore`. Such annotations are not inherited.
`*@AutoClose*`:: Denotes that the annotated field represents a resource that will be
xref:writing-tests/built-in-extensions.adoc#AutoClose[automatically closed] after test
execution. Such fields are inherited.
`*@Timeout*`:: Used to fail a test, test factory, test template, or lifecycle method if
its execution exceeds a given duration. Such annotations are inherited.
`*@TempDir*`:: Used to supply a
xref:writing-tests/built-in-extensions.adoc#TempDirectory[temporary directory] via field
injection or parameter injection in a test class constructor, lifecycle method, or test
method; located in the `org.junit.jupiter.api.io` package. Such fields are inherited.
`*@ExtendWith*`:: Used to
xref:extensions/registering-extensions.adoc#registration-declarative[register extensions declaratively]. Such
annotations are inherited.
`*@RegisterExtension*`:: Used to
xref:extensions/registering-extensions.adoc#registration-programmatic[register extensions programmatically] via fields.
Such fields are inherited.
WARNING: Some annotations may currently be _experimental_. Consult the table in
xref:api-evolution.adoc#experimental-apis[Experimental APIs] for details.
[[annotations]]
== Meta-Annotations and Composed Annotations
JUnit Jupiter annotations can be used as _meta-annotations_. That means that you can
define your own _composed annotation_ that will automatically _inherit_ the semantics of
its meta-annotations.
For example, instead of copying and pasting `@Tag("fast")` throughout your code base (see
xref:writing-tests/tagging-and-filtering.adoc[]), you can create a custom _composed annotation_
named `@Fast` as follows. `@Fast` can then be used as a drop-in replacement for
`@Tag("fast")`.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/Fast.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/Fast.kt[tags=user_guide]
----
--
====
The following `@Test` method demonstrates usage of the `@Fast` annotation.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
@Fast
@Test
void myFastTest() {
// ...
}
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
@Fast
@Test
fun myFastTest() {
// ...
}
----
--
====
You can even take that one step further by introducing a custom `@FastTest` annotation
that can be used as a drop-in replacement for `@Tag("fast")` _and_ `@Test`.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/FastTest.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/FastTest.kt[tags=user_guide]
----
--
====
JUnit automatically recognizes the following as a `@Test` method that is tagged with
"fast".
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
@FastTest
void myFastTest() {
// ...
}
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
@FastTest
fun myFastTest() {
// ...
}
----
--
====
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/assertions.adoc
================================================
= Assertions
JUnit Jupiter comes with many of the assertion methods that JUnit 4 has and adds a few
that lend themselves well to being used with Java lambdas. All JUnit Jupiter assertions
are `static` methods in the `{Assertions}` class.
Assertion methods optionally accept the assertion message as their third parameter, which
can be either a `String` or a `Supplier`.
When using a `Supplier` (e.g., a lambda expression), the message is evaluated
lazily. This can provide a performance benefit, especially if message construction is
complex or time-consuming, as it is only evaluated when the assertion fails.
[source,java,indent=0]
----
include::example$java/example/AssertionsDemo.java[tags=user_guide]
----
[[preemptive-timeouts]]
[WARNING]
.Preemptive Timeouts with `assertTimeoutPreemptively()`
====
The various `assertTimeoutPreemptively()` methods in the `Assertions` class execute
the provided `executable` or `supplier` in a different thread than that of the calling
code. This behavior can lead to undesirable side effects if the code that is executed
within the `executable` or `supplier` relies on `java.lang.ThreadLocal` storage.
One common example of this is the transactional testing support in the Spring Framework.
Specifically, Spring's testing support binds transaction state to the current thread (via
a `ThreadLocal`) before a test method is invoked. Consequently, if an `executable` or
`supplier` provided to `assertTimeoutPreemptively()` invokes Spring-managed components
that participate in transactions, any actions taken by those components will not be rolled
back with the test-managed transaction. On the contrary, such actions will be committed to
the persistent store (e.g., relational database) even though the test-managed transaction
is rolled back.
Similar side effects may be encountered with other frameworks that rely on
`ThreadLocal` storage.
====
[[kotlin]]
== Kotlin Assertion Support
JUnit Jupiter also comes with a few assertion methods that lend themselves well to being
used in https://kotlinlang.org/[Kotlin]. All JUnit Jupiter Kotlin assertions are top-level
functions in the `org.junit.jupiter.api` package.
[source,kotlin,indent=0]
----
include::example$kotlin/example/KotlinAssertionsDemo.kt[tags=user_guide]
----
[[third-party]]
== Third-party Assertion Libraries
Even though the assertion facilities provided by JUnit Jupiter are sufficient for many
testing scenarios, there are times when more power and additional functionality are
desired or required. In such cases, the JUnit team recommends the use of third-party
assertion libraries such as {AssertJ}, {Hamcrest}, {Truth}, etc. Developers are therefore
free to use the assertion library of their choice.
For example, the following demonstrates how to use the `assertThat()` support from AssertJ
in a JUnit Jupiter test. As long as the AssertJ library has been added to the classpath,
you can statically import methods such as `assertThat()`, `assertThatException()`, etc.
from `org.assertj.core.api.Assertions` and then use them in tests like in the
`assertWithAssertJ()` method below.
[source,java,indent=0]
----
include::example$java/example/AssertJAssertionsDemo.java[tags=user_guide]
----
[TIP]
.Excluding Jupiter’s Assertions From a Project’s Classpath
====
If you would like to enforce that all your tests use a certain third-party assertion
library instead of Jupiter's, you can set up a rule using {Checkstyle} or another static
analysis tool that fails the build if Jupiter's `Assertions` class is used.
[source,xml]
----
----
====
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/assumptions.adoc
================================================
= Assumptions
Assumptions are typically used whenever it does not make sense to continue execution of a
given test — for example, if the test depends on something that does not exist in the
current runtime environment.
* When an assumption is valid, the assumption method does not throw an exception, and
execution of the test continues as usual.
* When an assumption is invalid, the assumption method throws an exception of type
`org.opentest4j.TestAbortedException` to signal that the test should be aborted instead
of marked as a failure.
JUnit Jupiter comes with a subset of the _assumption_ methods that JUnit 4 provides and
adds a few that lend themselves well to being used with Java lambda expressions and method
references.
All JUnit Jupiter assumptions are static methods in the `{Assumptions}` class.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/AssumptionsDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/AssumptionsDemo.kt[tags=user_guide]
----
--
====
NOTE: It is also possible to use methods from JUnit 4's `org.junit.Assume` class for
assumptions. Specifically, JUnit Jupiter supports JUnit 4's `AssumptionViolatedException`
to signal that a test should be aborted instead of marked as a failure.
TIP: If you use AssertJ for assertions, you may also wish to use AssertJ for assumptions.
To do so, you can statically import the `assumeThat()` method from
`org.assertj.core.api.Assumptions` and then use AssertJ's fluent API to specify your
assumptions.
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/built-in-extensions.adoc
================================================
= Built-in Extensions
While the JUnit team encourages reusable extensions to be packaged and maintained in
separate libraries, JUnit Jupiter includes a few user-facing extension implementations
that are considered so generally useful that users shouldn't have to add another
dependency.
[[TempDirectory]]
== The @TempDir Extension
The `{TempDirectory}` extension is used to create and clean up a temporary
directory for an individual test or all tests in a test class. It is registered by
default. To use it, annotate a non-final, unassigned field of type `java.nio.file.Path` or
`java.io.File` with `{TempDir}` or add a parameter of type `java.nio.file.Path` or
`java.io.File` annotated with `@TempDir` to a test class constructor, lifecycle method, or
test method.
For example, the following test declares a parameter annotated with `@TempDir` for a
single test method, creates and writes to a file in the temporary directory, and checks
its content.
.A test method that requires a temporary directory
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/TempDirectoryDemo.java[tags=user_guide_parameter_injection]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_parameter_injection]
----
--
====
You can inject multiple temporary directories by specifying multiple annotated parameters.
.A test method that requires multiple temporary directories
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/TempDirectoryDemo.java[tags=user_guide_multiple_directories]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_multiple_directories]
----
--
====
The following example stores a _shared_ temporary directory in a `static` field. This
allows the same `sharedTempDir` to be used in all lifecycle methods and test methods of
the test class. For better isolation, you should use an instance field or constructor
injection so that each test method uses a separate directory.
.A test class that shares a temporary directory across test methods
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/TempDirectoryDemo.java[tags=user_guide_field_injection]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_field_injection]
----
--
====
The `@TempDir` annotation has an optional `cleanup` attribute that can be set to either
`NEVER`, `ON_SUCCESS`, or `ALWAYS`. If the cleanup mode is set to `NEVER`, the temporary
directory will not be deleted after the test completes. If it is set to `ON_SUCCESS`, the
temporary directory will only be deleted after the test if the test completed successfully.
The default cleanup mode is `ALWAYS`. You can use the
`junit.jupiter.tempdir.cleanup.mode.default`
xref:running-tests/configuration-parameters.adoc[configuration parameter] to override this default.
.A test class with a temporary directory that doesn't get cleaned up
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/TempDirectoryDemo.java[tags=user_guide_cleanup_mode]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_cleanup_mode]
----
--
====
[[TempDirFactory]]
=== Factories
`@TempDir` supports the programmatic creation of temporary directories via the optional
`factory` attribute. This is typically used to gain control over the temporary directory
creation, like defining the parent directory or the file system that should be used.
Factories can be created by implementing `{TempDirFactory}`. Implementations must provide a
no-args constructor and should not make any assumptions regarding when and how many times
they are instantiated, but they can assume that their `createTempDirectory(...)` and
`close()` methods will both be called once per instance, in this order, and from the same
thread.
The default implementation available in Jupiter delegates directory creation to
`java.nio.file.Files::createTempDirectory` which uses the default file system and the
system's temporary directory as the parent directory. It passes `junit-` as the prefix
string of the generated directory name to help identify it as a created by JUnit.
The following example defines a factory that uses the test name as the directory name
prefix instead of the `junit` constant value.
.A test class with a temporary directory having the test name as the directory name prefix
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/TempDirectoryDemo.java[tags=user_guide_factory_name_prefix]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_factory_name_prefix]
----
--
====
It is also possible to use an in-memory file system like `{Jimfs}` for the creation of the
temporary directory. The following example demonstrates how to achieve that.
.A test class with a temporary directory created with the Jimfs in-memory file system
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/TempDirectoryDemo.java[tags=user_guide_factory_jimfs]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_factory_jimfs]
----
--
====
`@TempDir` can also be used as a xref:writing-tests/annotations.adoc#annotations[meta-annotation] to
reduce repetition. The following code listing shows how to create a custom `@JimfsTempDir`
annotation that can be used as a drop-in replacement for
`@TempDir(factory = JimfsTempDirFactory.class)`.
.A custom annotation meta-annotated with `@TempDir`
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/TempDirectoryDemo.java[tags=user_guide_composed_annotation]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_composed_annotation]
----
--
====
The following example demonstrates how to use the custom `@JimfsTempDir` annotation.
.A test class using the custom annotation
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/TempDirectoryDemo.java[tags=user_guide_composed_annotation_usage]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_composed_annotation_usage]
----
--
====
Meta-annotations or additional annotations on the field or parameter the `TempDir`
annotation is declared on might expose additional attributes to configure the factory.
Such annotations and related attributes can be accessed via the `AnnotatedElementContext`
parameter of the `createTempDirectory(...)` method.
You can use the `junit.jupiter.tempdir.factory.default`
xref:running-tests/configuration-parameters.adoc[configuration parameter] to specify the
fully qualified class name of the `{TempDirFactory}` you would like to use by default. Just
like for factories configured via the `factory` attribute of the `@TempDir` annotation,
the supplied class has to implement the `{TempDirFactory}` interface. The default factory
will be used for all `@TempDir` annotations unless the `factory` attribute of the
annotation specifies a different factory.
In summary, the factory for a temporary directory is determined according to the following
precedence rules:
1. The `factory` attribute of the `@TempDir` annotation, if present
2. The default `{TempDirFactory}` configured via the configuration
parameter, if present
3. Otherwise, `org.junit.jupiter.api.io.TempDirFactory$Standard` will be used.
[[TempDirDeletionStrategy]]
=== Deletion
`@TempDir` supports the programmatic deletion of temporary directories via the optional
`deletionStrategy` attribute. This is typically used to gain control over what happens
when deletion of a file or directory fails.
Deletion strategies can be created by implementing `{TempDirDeletionStrategy}`.
Implementations must provide a no-args constructor.
Jupiter ships with two built-in deletion strategies:
* `{TempDirDeletionStrategyStandard}` (the default): attempts to delete all files and
directories recursively, retrying with permission resets on failure. Paths that still
cannot be deleted are scheduled for deletion on JVM exit, if possible. Additionally,
the test is failed.
* `{TempDirDeletionStrategyIgnoreFailures}`: delegates to `{TempDirDeletionStrategyStandard}`
but suppresses deletion failures by logging a warning instead of failing the test.
The following example uses `{TempDirDeletionStrategyIgnoreFailures}` so that any deletion
failures are only logged.
.A test class with a temporary directory that ignores deletion failures
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/TempDirectoryDemo.java[tags=user_guide_deletion_strategy]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/TempDirectoryDemo.kt[tags=user_guide_deletion_strategy]
----
--
====
You can use the `junit.jupiter.tempdir.deletion.strategy.default`
xref:running-tests/configuration-parameters.adoc[configuration parameter] to specify the
fully qualified class name of the `{TempDirDeletionStrategy}` you would like to use by
default. Just like for strategies configured via the `deletionStrategy` attribute of the
`@TempDir` annotation, the supplied class has to implement the `{TempDirDeletionStrategy}`
interface. The default strategy will be used for all `@TempDir` annotations unless the
`deletionStrategy` attribute of the annotation specifies a different strategy.
In summary, the deletion strategy for a temporary directory is determined according to the
following precedence rules:
1. The `deletionStrategy` attribute of the `@TempDir` annotation, if present
2. The default `{TempDirDeletionStrategy}` configured via the configuration parameter,
if present
3. Otherwise, `org.junit.jupiter.api.io.TempDirDeletionStrategy$Standard` will be used.
[[AutoClose]]
== The @AutoClose Extension
The `{AutoCloseExtension}` automatically closes resources associated with fields.
It is registered by default. To use it, annotate a field in a test class with
`{AutoClose}`.
`@AutoClose` fields may be either `static` or non-static. If the value of an `@AutoClose`
field is `null` when it is evaluated the field will be ignored, but a warning message will
be logged to inform you.
By default, `@AutoClose` expects the value of the annotated field to implement a `close()`
method that will be invoked to close the resource. However, developers can customize the
name of the close method via the `value` attribute. For example, `@AutoClose("shutdown")`
instructs JUnit to look for a `shutdown()` method to close the resource.
`@AutoClose` fields are inherited from superclasses. Furthermore, `@AutoClose` fields from
subclasses will be closed before `@AutoClose` fields in superclasses.
When multiple `@AutoClose` fields exist within a given test class, the order in which the
resources are closed depends on an algorithm that is deterministic but intentionally
nonobvious. This ensures that subsequent runs of a test suite close resources in the same
order, thereby allowing for repeatable builds.
The `AutoCloseExtension` implements the `AfterAllCallback` and
`TestInstancePreDestroyCallback` extension APIs. Consequently, a `static` `@AutoClose`
field will be closed after all tests in the current test class have completed, effectively
after `@AfterAll` methods have executed for the test class. A non-static `@AutoClose`
field will be closed before the current test class instance is destroyed. Specifically, if
the test class is configured with `@TestInstance(Lifecycle.PER_METHOD)` semantics, a
non-static `@AutoClose` field will be closed after the execution of each test method, test
factory method, or test template method. However, if the test class is configured with
`@TestInstance(Lifecycle.PER_CLASS)` semantics, a non-static `@AutoClose` field will not
be closed until the current test class instance is no longer needed, which means after
`@AfterAll` methods and after all `static` `@AutoClose` fields have been closed.
The following example demonstrates how to annotate an instance field with `@AutoClose` so
that the resource is automatically closed after test execution. In this example, we assume
that the default `@TestInstance(Lifecycle.PER_METHOD)` semantics apply.
.A test class using `@AutoClose` to close a resource
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/AutoCloseDemo.java[tags=user_guide_example]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/AutoCloseDemo.kt[tags=user_guide_example]
----
--
====
<1> Annotate an instance field with `@AutoClose`.
<2> `WebClient` implements `java.lang.AutoCloseable` which defines a `close()` method that
will be invoked after each `@Test` method.
[[DefaultLocaleAndTimeZone]]
== The @DefaultLocale and @DefaultTimeZone Extensions
The `{DefaultLocale}` and `{DefaultTimeZone}` annotations can be used to change the values
returned from `Locale.getDefault()` and `TimeZone.getDefault()`, respectively, which are
often used implicitly when no specific locale or time zone is chosen. Both annotations
work on the test class level and on the test method level, and are inherited from
higher-level containers. After the annotated element has been executed, the initial
default value is restored.
[[DefaultLocale]]
=== @DefaultLocale
The default `Locale` can be specified using an
{jdk-javadoc-base-url}/java.base/java/util/Locale.html#forLanguageTag-java.lang.String-[IETF BCP 47 language tag string].
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tags=default_locale_language]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tags=default_locale_language]
----
--
====
Alternatively, the default `Locale` can be created using the following attributes from
which a {jdk-javadoc-base-url}/java.base/java/util/Locale.Builder.html[`Locale.Builder`]
can create an instance:
* `language`
* `language` and `country`
* `language`, `country`, and `variant`
NOTE: The variant needs to be a string which follows the
https://www.rfc-editor.org/rfc/rfc5646.html[IETF BCP 47 / RFC 5646] syntax.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tag=default_locale_language_alternatives]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tag=default_locale_language_alternatives]
----
--
====
Mixing language tag configuration (via the annotation's `value` attribute) and
attribute-based configuration will cause an exception to be thrown. Furthermore, a
`variant` can only be specified if `country` is also specified. Otherwise, an exception
will be thrown.
Method-level `@DefaultLocale` configuration overrides class-level configuration.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tag=default_locale_class_level]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tag=default_locale_class_level]
----
--
====
NOTE: With class-level configuration, the specified locale is set before and reset after
each individual test in the annotated class.
If your use case is not covered, you can implement the `{LocaleProvider}` interface.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tag=default_locale_with_provider]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tag=default_locale_with_provider]
----
--
====
NOTE: The provider implementation must have a no-args (or default) constructor.
[[DefaultTimeZone]]
=== @DefaultTimeZone
The default `TimeZone` is specified according to the
{jdk-javadoc-base-url}/java.base/java/util/TimeZone.html#getTimeZone(java.lang.String)[TimeZone.getTimeZone(String)]
method.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tag=default_timezone_zone]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tag=default_timezone_zone]
----
--
====
Method-level `@DefaultTimeZone` configuration overrides class-level configuration.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tag=default_timezone_class_level]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tag=default_timezone_class_level]
----
--
====
NOTE: With class-level configuration, the specified time zone is set before and reset
after each individual test in the annotated class.
If your use case is not covered, you can implement the `{TimeZoneProvider}` interface.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DefaultLocaleTimezoneExtensionDemo.java[tag=default_time_zone_with_provider]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt[tag=default_time_zone_with_provider]
----
--
====
NOTE: The provider implementation must have a no-args (or default) constructor.
=== Thread Safety
Since the default locale and time zone are global state, reading and writing them during
xref:writing-tests/parallel-execution.adoc[parallel test execution] can lead to unpredictable
results and flaky tests. The `@DefaultLocale` and `@DefaultTimeZone` extensions are
prepared for that and tests annotated with them will never execute in parallel (thanks to
`{ResourceLock}`) to guarantee correct test results.
However, this does not cover all possible cases. Tested code that reads or writes the
default locale or time zone _independently_ of the extensions can still run in parallel
and may thus behave erratically when, for example, such code unexpectedly reads a locale
set by the extension in another thread. Consequently, tests that cover code that reads or
writes the default locale or time zone need to be annotated with one of the following
respective annotations.
* `{ReadsDefaultLocale}`
* `{ReadsDefaultTimeZone}`
* `{WritesDefaultLocale}`
* `{WritesDefaultTimeZone}`
Tests annotated with one of the above annotations will never execute in parallel with
tests annotated with `@DefaultLocale` or `@DefaultTimeZone`.
[[system-properties]]
== The System Properties Extension
The system properties extension supports a set of annotations that work together to clear,
set, and restore JVM system properties.
[[system-properties-clear-and-set]]
=== @ClearSystemProperty and @SetSystemProperty
The `{ClearSystemProperty}` and `{SetSystemProperty}` annotations can be used to clear and
set, respectively, the values of JVM system properties for test execution. Both
annotations work on the test method and class level and are repeatable, combinable, and
inherited from higher-level containers. After the annotated method has been executed, the
properties configured in the annotation will be restored to their original value or the
value of the higher-level container, or will be cleared if they did not previously have a
value. Other system properties that are changed during the test are _not_ restored (unless
restoration is <> via
`{RestoreSystemProperties}`).
For example, clearing a system property for test execution can be done as follows.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_clear_simple]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_clear_simple]
----
--
====
The following demonstrates how to set a system property for test execution.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_set_simple]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_set_simple]
----
--
====
As mentioned before, both annotations are repeatable, and they can also be combined.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_using_set_and_clear]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_using_set_and_clear]
----
--
====
Note that class-level configuration is overridden by method-level configuration.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_using_at_class_level]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_using_at_class_level]
----
--
====
[NOTE]
====
Method-level configuration is visible in both `@BeforeEach` methods and `@AfterEach`
methods (see
xref:extensions/relative-execution-order-of-user-code-and-extensions.adoc#overview[user
code and extension code execution order]).
With class-level configuration, the specified system properties are cleared or set before
and reset after each individual test in the annotated class.
====
[[system-properties-restore]]
=== @RestoreSystemProperties
The `{RestoreSystemProperties}` annotation can be used to restore changes to system
properties made directly in the test or in the code being tested. Although
`@ClearSystemProperty` and `@SetSystemProperty` clear or set properties and values that
are statically declared, they do not allow property values to be calculated dynamically.
Thus, there are times you may want to directly set properties in your test code.
`@RestoreSystemProperties` can be placed on test methods or test classes and will
completely restore all system properties to their original state after the test or test
class has finished.
[NOTE]
====
During the execution of the annotated scope, the JVM system properties are set to a clone
of the original `Properties` object. However, the clone does not include
{jdk-javadoc-base-url}/java.base/java/util/Properties.html#defaults[the defaults] from the
original.
Consequently, the extension will perform a best effort attempt to detect default properties
and fail if any were detected. For classes that extend `Properties` it is assumed that
`clone()` is implemented with sufficient fidelity.
====
In the following example, `@RestoreSystemProperties` is used on a test method, ensuring
any changes made in that method are restored.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_restore_test]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_restore_test]
----
--
====
When `@RestoreSystemProperties` is used on a test class, any changes to system properties
during the entire lifecycle of the test class, including test methods, `@BeforeAll`,
`@BeforeEach`, and 'after' methods, are restored after the lifecycle of the test class is
complete. In addition, the annotation is inherited by each test method just as if each one
were annotated with `@RestoreSystemProperties`.
In the following example, both test methods see the system property changes made in
`@BeforeAll` and `@BeforeEach`; however, the test methods are isolated from each other
(`isolatedTest2` does not _see_ changes made in `isolatedTest1`). As shown in the second
example below, the class-level `@RestoreSystemProperties` annotation ensures that system
property changes made within the annotated class are completely restored after the class's
lifecycle, ensuring that changes are not visible to `SomeOtherTestClass`. Note that
`SomeOtherTestClass` uses the `@ReadsSystemProperty` annotation, which ensures that JUnit
does not schedule the class to run during any test known to modify system properties (see
<>).
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_class_restore_setup]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_class_restore_setup]
----
--
====
Some other test class, running later:
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_class_restore_isolated_class]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_class_restore_isolated_class]
----
--
====
[[system-properties-combined-usage]]
=== Combining @ClearSystemProperty, @SetSystemProperty, and @RestoreSystemProperties
The three system property annotations can be combined, which can be useful when some
system properties are set dynamically in code and others are not. For instance, imagine
you need to test an image generation utility that takes configuration from system
properties. Basic configuration can be specified declaratively using the `Clear` and `Set`
annotations, and the image size could be set programmatically.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/SystemPropertyExtensionDemo.java[tag=systemproperty_method_combine_all_test]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/SystemPropertyExtensionDemo.kt[tag=systemproperty_method_combine_all_test]
----
--
====
[NOTE]
====
Using `@RestoreSystemProperties` is not necessary to restore system properties modified
via `@ClearSystemProperty` or `@SetSystemProperty` since they both automatically restore
the referenced properties. `@RestoreSystemProperties` is only needed if system properties
are modified during a test in some way _other than_ via the `Clear` and `Set` annotations.
====
[[system-properties-thread-safety]]
=== Thread Safety
Since system properties are global state, reading and writing them during
xref:writing-tests/parallel-execution.adoc[parallel execution] can lead to unpredictable
results and flaky tests. The system property extension is prepared for that and tests
annotated with `@ClearSystemProperty`, `@SetSystemProperty`, or `@RestoreSystemProperties`
will never execute in parallel (thanks to
xref:writing-tests/parallel-execution.adoc#synchronization[resource locks]) to guarantee
correct test results.
However, this does not cover all possible cases. Tested code that reads or writes system
properties _independently_ of the extension can still run in parallel to it and may thus
behave erratically when, for example, it unexpectedly reads a property set by the
extension in another thread. Tests that cover code that reads or writes system properties
need to be annotated with the respective annotation:
* `{ReadsSystemProperty}`
* `{WritesSystemProperty}` (though consider using `@RestoreSystemProperties` instead)
Tests annotated in this way will never execute in parallel with tests annotated with
`@ClearSystemProperty`, `@SetSystemProperty`, or `@RestoreSystemProperties`.
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/class-templates.adoc
================================================
= Class Templates
A `{ClassTemplate}` is not a regular test class but rather a template for the contained
test cases. As such, it is designed to be invoked multiple times depending on invocation
contexts returned by the registered providers. Thus, it must be used in conjunction with a
registered `{ClassTemplateInvocationContextProvider}` extension.
Each invocation of a class template behaves like the execution of a regular test class
with full support for the same lifecycle callbacks and extensions. Please refer to
xref:extensions/providing-invocation-contexts-for-class-templates.adoc[] for usage examples.
NOTE: xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Classes] are a built-in
specialization of class templates.
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/conditional-test-execution.adoc
================================================
= Conditional Test Execution
The xref:extensions/conditional-test-execution.adoc[`ExecutionCondition`] extension API in JUnit Jupiter allows
developers to either _enable_ or _disable_ a test class or test method based on certain
conditions _programmatically_. The simplest example of such a condition is the built-in
`{DisabledCondition}` which supports the `{Disabled}` annotation (see
xref:writing-tests/disabling-tests.adoc[]).
In addition to `@Disabled`, JUnit Jupiter also supports several other annotation-based
conditions in the `org.junit.jupiter.api.condition` package that allow developers to
enable or disable test classes and test methods _declaratively_. If you wish to provide
details about why they might be disabled, every annotation associated with these built-in
conditions has a `disabledReason` attribute available for that purpose.
When multiple `ExecutionCondition` extensions are registered, a test class or test method
is disabled as soon as one of the conditions returns _disabled_. If a test class is
disabled, all test methods within that class are automatically disabled as well. If a test
method is disabled, that prevents execution of the test method and method-level lifecycle
callbacks such as `@BeforeEach` methods, `@AfterEach` methods, and corresponding extension
APIs. However, that does not prevent the test class from being instantiated, and it does
not prevent the execution of class-level lifecycle callbacks such as `@BeforeAll` methods,
`@AfterAll` methods, and corresponding extension APIs.
See xref:extensions/conditional-test-execution.adoc[`ExecutionCondition`] and the following sections for
details.
[TIP]
.Composed Annotations
====
Note that any of the _conditional_ annotations listed in the following sections may also
be used as a meta-annotation in order to create a custom _composed annotation_. For
example, the `@TestOnMac` annotation in the
<> shows how you can
combine `@Test` and `@EnabledOnOs` in a single, reusable annotation.
====
[NOTE]
====
_Conditional_ annotations in JUnit Jupiter are not `@Inherited`. Consequently, if you wish
to apply the same semantics to subclasses, each conditional annotation must be redeclared
on each subclass.
====
[WARNING]
====
Unless otherwise stated, each of the _conditional_ annotations listed in the following
sections can only be declared once on a given test interface, test class, or test method.
If a conditional annotation is directly present, indirectly present, or meta-present
multiple times on a given element, only the first such annotation discovered by JUnit will
be used; any additional declarations will be silently ignored. Note, however, that each
conditional annotation may be used in conjunction with other conditional annotations in
the `org.junit.jupiter.api.condition` package.
====
[[os]]
== Operating System and Architecture Conditions
A container or test may be enabled or disabled on a particular operating system,
architecture, or combination of both via the `{EnabledOnOs}` and `{DisabledOnOs}`
annotations.
[[os-demo]]
.Conditional execution based on operating system
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_os]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_os]
----
--
====
[[architectures-demo]]
.Conditional execution based on architecture
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_architecture]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_architecture]
----
--
====
[[jre]]
== Java Runtime Environment Conditions
A container or test may be enabled or disabled on particular versions of the Java Runtime
Environment (JRE) via the `{EnabledOnJre}` and `{DisabledOnJre}` annotations or on a
particular range of versions of the JRE via the `{EnabledForJreRange}` and
`{DisabledForJreRange}` annotations. The range effectively defaults to `JRE.JAVA_8` as the
lower bound and `JRE.OTHER` as the upper bound, which allows usage of half open ranges.
The following listing demonstrates the use of these annotations with predefined {JRE} enum
constants.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_jre]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_jre]
----
--
====
Since the enum constants defined in {JRE} are static for any given JUnit release, you
might find that you need to configure a Java version that is not supported by the `JRE`
enum. For example, when JUnit Jupiter 5.12 was released the `JRE` enum defined `JAVA_25`
as the highest supported Java version. However, you may wish to run your tests against
later versions of Java. To support such use cases, you can specify arbitrary Java versions
via the `versions` attributes in `@EnabledOnJre` and `@DisabledOnJre` and via the
`minVersion` and `maxVersion` attributes in `@EnabledForJreRange` and
`@DisabledForJreRange`.
The following listing demonstrates the use of these annotations with arbitrary Java
versions.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_jre_arbitrary_versions]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_jre_arbitrary_versions]
----
--
====
[[native]]
== Native Image Conditions
A container or test may be enabled or disabled within a
https://www.graalvm.org/reference-manual/native-image/[GraalVM native image] via the
`{EnabledInNativeImage}` and `{DisabledInNativeImage}` annotations. These annotations are
typically used when running tests within a native image using the Gradle and Maven
plug-ins from the GraalVM https://graalvm.github.io/native-build-tools/latest/[Native
Build Tools] project.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_native]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_native]
----
--
====
[[system-properties]]
== System Property Conditions
A container or test may be enabled or disabled based on the value of the `named` JVM
system property via the `{EnabledIfSystemProperty}` and `{DisabledIfSystemProperty}`
annotations. The value supplied via the `matches` attribute will be interpreted as a
regular expression.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_system_property]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_system_property]
----
--
====
[TIP]
====
`{EnabledIfSystemProperty}` and `{DisabledIfSystemProperty}` are _repeatable annotations_.
Consequently, these annotations may be declared multiple times on a test interface, test
class, or test method. Specifically, these annotations will be found if they are directly
present, indirectly present, or meta-present on a given element.
====
[[environment-variables]]
== Environment Variable Conditions
A container or test may be enabled or disabled based on the value of the `named`
environment variable from the underlying operating system via the
`{EnabledIfEnvironmentVariable}` and `{DisabledIfEnvironmentVariable}` annotations. The
value supplied via the `matches` attribute will be interpreted as a regular expression.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_environment_variable]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_environment_variable]
----
--
====
[TIP]
====
`{EnabledIfEnvironmentVariable}` and `{DisabledIfEnvironmentVariable}` are _repeatable
annotations_. Consequently, these annotations may be declared multiple times on a test
interface, test class, or test method. Specifically, these annotations will be found if
they are directly present, indirectly present, or meta-present on a given element.
====
[[custom]]
== Custom Conditions
As an alternative to implementing an xref:extensions/conditional-test-execution.adoc[`ExecutionCondition`], a
container or test may be enabled or disabled based on a _condition method_ configured via
the `{EnabledIf}` and `{DisabledIf}` annotations. A condition method must have a `boolean`
return type and may accept either no arguments or a single `ExtensionContext` argument.
The following test class demonstrates how to configure a local method named
`customCondition` via `@EnabledIf` and `@DisabledIf`.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/ConditionalTestExecutionDemo.java[tags=user_guide_custom]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/ConditionalTestExecutionDemo.kt[tags=user_guide_custom]
----
--
====
Alternatively, the condition method can be located outside the test class. In this case,
it must be referenced by its _fully qualified name_ as demonstrated in the following
example.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
package example;
include::example$java/example/ExternalCustomConditionDemo.java[tags=user_guide_external_custom_condition]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
package example.kotlin
include::example$kotlin/example/kotlin/ExternalCustomConditionDemo.kt[tags=user_guide_external_custom_condition]
----
--
====
[NOTE]
====
There are several cases where a condition method would need to be `static`:
- when `@EnabledIf` or `@DisabledIf` is used at class level
- when `@EnabledIf` or `@DisabledIf` is used on a `@ParameterizedTest` or a
`@TestTemplate` method
- when the condition method is located in an external class
In any other case, you can use either static methods or instance methods as condition
methods.
====
[TIP]
====
It is often the case that you can use an existing static method in a utility class as a
custom condition.
For example, `java.awt.GraphicsEnvironment` provides a `public static boolean isHeadless()`
method that can be used to determine if the current environment does not support a
graphical display. Thus, if you have a test that depends on graphical support you can
disable it when such support is unavailable as follows.
[tabs]
======
Java::
+
--
[source,java,indent=0]
----
@DisabledIf(value = "java.awt.GraphicsEnvironment#isHeadless",
disabledReason = "headless environment")
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
@DisabledIf(value = "java.awt.GraphicsEnvironment#isHeadless",
disabledReason = "headless environment")
----
--
======
====
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/definitions.adoc
================================================
= Definitions
== Platform Concepts
Container::
a node in the test tree that contains other containers or tests as its children (e.g. a _test class_).
Test::
a node in the test tree that verifies expected behavior when executed (e.g. a `@Test` method).
== Jupiter Concepts
Lifecycle Method::
any method that is directly annotated or meta-annotated with
`@BeforeAll`, `@AfterAll`, `@BeforeEach`, or `@AfterEach`.
Test Class::
any top-level class, `static` member class, or xref:writing-tests/nested-tests.adoc[`@Nested` class] that contains at least one _test method_, i.e. a _container_.
Test classes must not be `abstract` and must have a single constructor.
Java `record` classes are supported as well.
Test Method::
any instance method that is directly annotated or meta-annotated with
`@Test`, `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`, or `@TestTemplate`.
With the exception of `@Test`, these create a _container_ in the test tree that groups
_tests_ or, potentially (for `@TestFactory`), other _containers_.
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/dependency-injection-for-constructors-and-methods.adoc
================================================
= Dependency Injection for Constructors and Methods
In JUnit Jupiter, both test constructors and methods are permitted to have parameters.
This allows for greater flexibility and enables _Dependency Injection_ for constructors
and methods.
`{ParameterResolver}` defines the API for test extensions that wish to _dynamically_
resolve parameters at runtime. If a _test class_ constructor, a _test method_, or a
_lifecycle method_ (see xref:writing-tests/definitions.adoc[]) accepts a parameter, the parameter
must be resolved at runtime by a registered `ParameterResolver`.
[[built-in-parameter-resolvers]]
== Built-In Parameter Resolvers
There are currently three built-in resolvers that are registered automatically.
[[test-info]]
=== TestInfo
If a constructor or method parameter is of type `{TestInfo}`, the
`{TestInfoParameterResolver}` will supply an instance of `TestInfo` corresponding to the
current container or test as the value for the parameter. The `TestInfo` can then be used
to retrieve information about the current container or test such as the display name, the
test class, the test method, and associated tags. The display name is either a technical
name, such as the name of the test class or test method, or a custom name configured via
`@DisplayName`.
`{TestInfo}` acts as a drop-in replacement for the `TestName` rule from JUnit 4. The
following demonstrates how to have `TestInfo` injected into a `@BeforeAll` method, test
class constructor, `@BeforeEach` method, and `@Test` method.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/TestInfoDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/TestInfoDemo.kt[tags=user_guide]
----
--
====
[[repetition-info]]
=== RepetitionInfo
If a method parameter in a `@RepeatedTest`, `@BeforeEach`, or `@AfterEach` method is of
type `{RepetitionInfo}`, the `{RepetitionExtension}` will supply an instance of
`RepetitionInfo`. `RepetitionInfo` can then be used to retrieve information about the
current repetition, the total number of repetitions, the number of repetitions that have
failed, and the failure threshold for the corresponding `@RepeatedTest`. Note, however,
that `RepetitionExtension` is not registered outside the context of a `@RepeatedTest`. See
xref:writing-tests/repeated-tests.adoc#examples[Repeated Test Examples].
[[test-reporter]]
=== TestReporter
If a constructor or method parameter is of type `{TestReporter}`, the
`{TestReporterParameterResolver}` will supply an instance of `TestReporter`. The
`TestReporter` can be used to publish additional data about the current test run or attach
files to it. The data can be consumed in a `{TestExecutionListener}` via the
`reportingEntryPublished()` or `fileEntryPublished()` method, respectively. This allows
them to be viewed in IDEs or included in reports.
In JUnit Jupiter you should use `TestReporter` where you used to print information to
`stdout` or `stderr` in JUnit 4. Some IDEs print report entries to `stdout` or display
them in the user interface for test results.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/TestReporterDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/TestReporterDemo.kt[tags=user_guide]
----
--
====
[[custom-parameter-resolvers]]
== Custom Parameter Resolvers
NOTE: Custom parameter resolvers must be explicitly enabled by
xref:extensions/registering-extensions.adoc[registering] appropriate
xref:extensions/overview.adoc[extensions].
Check out the `xref:extensions/registering-extensions.adoc#RandomNumberExtension[RandomNumberExtension]`
for an example of a custom `{ParameterResolver}`. While not intended to be production-ready,
it demonstrates the simplicity and expressiveness of both the extension model and the
parameter resolution process. `MyRandomParametersTest` demonstrates how to inject random
values into a constructor and a `@Test` method.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/MyRandomParametersTest.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/MyRandomParametersTest.kt[tags=user_guide]
----
--
====
For real-world use cases, check out the source code for the `{MockitoExtension}` and the
`{SpringExtension}`.
When the type of the parameter to inject is the only condition for your
`{ParameterResolver}`, you can use the generic `{TypeBasedParameterResolver}` base class.
The `supportsParameters` method is implemented behind the scenes and supports
parameterized types.
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/disabling-tests.adoc
================================================
= Disabling Tests
Entire test classes or individual test methods may be _disabled_ via the `{Disabled}`
annotation, via one of the annotations discussed in
xref:writing-tests/conditional-test-execution.adoc[], or via a custom xref:extensions/conditional-test-execution.adoc[`ExecutionCondition`].
When `@Disabled` is applied at the class level, all test methods within that class are
automatically disabled as well.
If a test method is disabled via `@Disabled`, that prevents execution of the test method
and method-level lifecycle callbacks such as `@BeforeEach` methods, `@AfterEach` methods,
and corresponding extension APIs. However, that does not prevent the test class from being
instantiated, and it does not prevent the execution of class-level lifecycle callbacks
such as `@BeforeAll` methods, `@AfterAll` methods, and corresponding extension APIs.
Here's a `@Disabled` test class.
[source,java,indent=0]
----
include::example$java/example/DisabledClassDemo.java[tags=user_guide]
----
And here's a test class that contains a `@Disabled` test method.
[source,java,indent=0]
----
include::example$java/example/DisabledTestsDemo.java[tags=user_guide]
----
[TIP]
====
`@Disabled` may be declared without providing a _reason_; however, the JUnit team
recommends that developers provide a short explanation for why a test class or test
method has been disabled. Consequently, the above examples both show the use of a reason
-- for example, `@Disabled("Disabled until bug #42 has been resolved")`. Some development
teams even require the presence of issue tracking numbers in the _reason_ for automated
traceability, etc.
====
[NOTE]
====
`@Disabled` is not `@Inherited`. Consequently, if you wish to disable a class whose
superclass is `@Disabled`, you must redeclare `@Disabled` on the subclass.
====
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/display-names.adoc
================================================
= Display Names
Test classes and test methods can declare custom display names via `@DisplayName` -- with
spaces, special characters, and even emojis -- that will be displayed in test reports and
by test runners and IDEs.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DisplayNameDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DisplayNameDemo.kt[tags=user_guide]
----
--
====
[TIP]
====
In Kotlin, you can use backtick-enclosed function names as an alternative to
`@DisplayName` for simple display names:
[source,kotlin,indent=0]
----
@Test
fun `custom test name containing spaces`() {
// will be displayed as "custom test name containing spaces"
}
----
====
[NOTE]
====
Control characters in text-based arguments in display names for parameterized tests are
escaped by default. See xref:writing-tests/parameterized-classes-and-tests.adoc#display-names-quoted-text[Quoted Text-based Arguments]
for details.
Any remaining ISO control characters in a display name will be replaced as follows.
[cols="25%,15%,60%"]
|===
| Original | Replacement | Description
| ```\r```
| ``````
| Textual representation of a carriage return
| ```\n```
| ``````
| Textual representation of a line feed
| Other control character
| ```�```
| Unicode replacement character (U+FFFD)
|===
====
[[generator]]
== Display Name Generators
JUnit Jupiter supports custom display name generators that can be configured via the
`@DisplayNameGeneration` annotation.
Generators can be created by implementing the `DisplayNameGenerator` API. The following
table lists the default display name generators available in Jupiter.
[cols="20,80"]
|===
| DisplayNameGenerator | Behavior
| `Standard` | Matches the standard display name generation behavior in place since JUnit Jupiter was introduced.
| `Simple` | Extends the functionality of `Standard` by removing trailing parentheses for methods with no parameters.
| `ReplaceUnderscores` | Replaces underscores with spaces.
| `IndicativeSentences` | Generates complete sentences by concatenating the names of the test and the enclosing classes.
|===
NOTE: Values provided via `@DisplayName` annotations always take precedence over display
names generated by a `DisplayNameGenerator`.
======
The following example demonstrates the use of the `ReplaceUnderscores` display name
generator.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DisplayNameGeneratorDemo.java[tags=user_guide_replace_underscores]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DisplayNameGeneratorDemo.kt[tags=user_guide_replace_underscores]
----
--
====
Running the above test class results in the following display names.
```
A year is not supported ✔
├─ if it is zero ✔
└─ A negative value for year is not supported by the leap year computation. ✔
├─ For example, year -1 is not supported. ✔
└─ For example, year -4 is not supported. ✔
```
======
======
With the `IndicativeSentences` display name generator, you can customize the separator and
the underlying generator by using `@IndicativeSentencesGeneration` as shown in the
following example.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DisplayNameGeneratorDemo.java[tags=user_guide_indicative_sentences]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DisplayNameGeneratorDemo.kt[tags=user_guide_indicative_sentences]
----
--
====
Running the above test class results in the following display names.
```
A year is a leap year ✔
├─ A year is a leap year -> if it is divisible by 4 but not by 100 ✔
└─ A year is a leap year -> if it is one of the following years ✔
├─ Year 2016 is a leap year. ✔
├─ Year 2020 is a leap year. ✔
└─ Year 2048 is a leap year. ✔
```
======
======
With `IndicativeSentences`, you can optionally specify custom sentence fragments via the
`@SentenceFragment` annotation as demonstrated in the following example.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DisplayNameGeneratorDemo.java[tags=user_guide_custom_sentence_fragments]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DisplayNameGeneratorDemo.kt[tags=user_guide_custom_sentence_fragments]
----
--
====
Running the above test class results in the following display names.
```
A year is a leap year ✔
├─ A year is a leap year, if it is divisible by 4 but not by 100 ✔
└─ A year is a leap year, if it is one of the following years ✔
├─ 2016 ✔
├─ 2020 ✔
└─ 2048 ✔
```
======
[[generator-default]]
== Setting the Default Display Name Generator
You can use the `junit.jupiter.displayname.generator.default`
xref:running-tests/configuration-parameters.adoc[configuration parameter] to specify the fully qualified
class name of the `DisplayNameGenerator` you would like to use by default. Just like for
display name generators configured via the `@DisplayNameGeneration` annotation, the
supplied class has to implement the `DisplayNameGenerator` interface. The default display
name generator will be used for all tests unless the `@DisplayNameGeneration` annotation
is present on an enclosing test class or test interface. Values provided via
`@DisplayName` annotations always take precedence over display names generated by a
`DisplayNameGenerator`.
For example, to use the `ReplaceUnderscores` display name generator by default, you should
set the configuration parameter to the corresponding fully qualified class name (e.g., in
`src/test/resources/junit-platform.properties`):
[source,properties,indent=0]
----
junit.jupiter.displayname.generator.default = \
org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores
----
Similarly, you can specify the fully qualified name of any custom class that implements
`DisplayNameGenerator`.
[[generator-precedence-rules]]
In summary, the display name for a test class or method is determined according to the
following precedence rules:
1. value of the `@DisplayName` annotation, if present
2. by calling the `DisplayNameGenerator` specified in the `@DisplayNameGeneration`
annotation, if present
3. by calling the default `DisplayNameGenerator` configured via the configuration
parameter, if present
4. by calling `org.junit.jupiter.api.DisplayNameGenerator.Standard`
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/dynamic-tests.adoc
================================================
= Dynamic Tests
The standard `@Test` annotation in JUnit Jupiter described in
xref:writing-tests/annotations.adoc[] is very similar to the `@Test` annotation in JUnit 4. Both
describe methods that implement test cases. These test cases are static in the sense that
they are fully specified at compile time, and their behavior cannot be changed by
anything happening at runtime. _Assumptions provide a basic form of dynamic behavior but
are intentionally rather limited in their expressiveness._
In addition to these standard tests a completely new kind of test programming model has
been introduced in JUnit Jupiter. This new kind of test is a _dynamic test_ which is
generated at runtime by a factory method that is annotated with `@TestFactory`.
In contrast to `@Test` methods, a `@TestFactory` method is not itself a test case but
rather a factory for test cases. Thus, a dynamic test is the product of a factory.
Technically speaking, a `@TestFactory` method must return a single `DynamicNode` or a
_stream_ of `DynamicNode` instances or any of its subclasses. In this context, a "stream"
is anything that JUnit can reliably convert into a `Stream`, such as `Stream`,
`Collection`, `Iterator`, `Iterable`, an array of objects, or any type that provides an
`iterator(): Iterator` method (such as, for example, a `kotlin.sequences.Sequence`).
Instantiable subclasses of `DynamicNode` are `DynamicContainer` and `DynamicTest`.
`DynamicContainer` instances are composed of a _display name_ and a list of dynamic child
nodes, enabling the creation of arbitrarily nested hierarchies of dynamic nodes.
`DynamicTest` instances will be executed lazily, enabling dynamic and even
non-deterministic generation of test cases.
Any `Stream` returned by a `@TestFactory` will be properly closed by calling
`stream.close()`, making it safe to use a resource such as `Files.lines()`.
As with `@Test` methods, `@TestFactory` methods must not be `private` or `static` and may
optionally declare parameters to be resolved by `ParameterResolvers`.
A `DynamicTest` is a test case generated at runtime. It is composed of a _display name_
and an `Executable`. `Executable` is a `@FunctionalInterface` which means that the
implementations of dynamic tests can be provided as _lambda expressions_ or _method
references_.
.Dynamic Test Lifecycle
WARNING: The execution lifecycle of a dynamic test is quite different than it is for a
standard `@Test` case. Specifically, there are no lifecycle callbacks for individual
dynamic tests. This means that `@BeforeEach` and `@AfterEach` methods and their
corresponding extension callbacks are executed for the `@TestFactory` method but not for
each _dynamic test_. In other words, if you access fields from the test instance within a
lambda expression for a dynamic test, those fields will not be reset by callback methods
or extensions between the execution of individual dynamic tests generated by the same
`@TestFactory` method.
[[examples]]
== Dynamic Test Examples
The following `DynamicTestsDemo` class demonstrates several examples of test factories
and dynamic tests.
The first method returns an invalid return type and will cause a warning to be reported by
JUnit during test discovery. Such methods are not executed.
The next six methods demonstrate the generation of a `Collection`, `Iterable`, `Iterator`,
array, or `Stream` of `DynamicTest` instances. Most of these examples do not really
exhibit dynamic behavior but merely demonstrate the supported return types in principle.
However, `dynamicTestsFromStream()` and `dynamicTestsFromIntStream()` demonstrate how to
generate dynamic tests for a given set of strings or a range of input numbers.
The next method is truly dynamic in nature. `generateRandomNumberOfTests()` implements an
`Iterator` that generates random numbers, a display name generator, and a test executor
and then provides all three to `DynamicTest.stream()`. Although the non-deterministic
behavior of `generateRandomNumberOfTests()` is of course in conflict with test
repeatability and should thus be used with care, it serves to demonstrate the
expressiveness and power of dynamic tests.
The next method is similar to `generateRandomNumberOfTests()` in terms of flexibility;
however, `dynamicTestsFromStreamFactoryMethod()` generates a stream of dynamic tests from
an existing `Stream` via the `DynamicTest.stream()` factory method.
For demonstration purposes, the `dynamicNodeSingleTest()` method generates a single
`DynamicTest` instead of a stream, and the `dynamicNodeSingleContainer()` method generates
a nested hierarchy of dynamic tests utilizing `DynamicContainer`.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DynamicTestsDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DynamicTestsDemo.kt[tags=user_guide]
----
--
====
[[named-support]]
== Dynamic Tests and Named
In some cases, it can be more natural to specify inputs together with a descriptive name
using the {Named} API and the corresponding `stream()` factory methods on `DynamicTest` as
shown in the first example below. The second example takes it one step further and allows
to provide the code block that should be executed by implementing the `Executable`
interface along with `Named` via the `NamedExecutable` base class.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DynamicTestsNamedDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DynamicTestsNamedDemo.kt[tags=user_guide]
----
--
====
[[uri-test-source]]
== URI Test Sources for Dynamic Tests
The JUnit Platform provides `TestSource`, a representation of the source of a test or
container used to navigate to its location by IDEs and build tools.
The `TestSource` for a dynamic test or dynamic container can be constructed from a
`java.net.URI` which can be supplied via the `DynamicTest.dynamicTest(String, URI,
Executable)` or `DynamicContainer.dynamicContainer(String, URI, Stream)` factory method,
respectively. The `URI` will be converted to one of the following `TestSource`
implementations.
`ClasspathResourceSource` ::
If the `URI` contains the `classpath` scheme -- for example,
`classpath:/test/foo.xml?line=20,column=2`.
`DirectorySource` ::
If the `URI` represents a directory present in the file system.
`FileSource` ::
If the `URI` represents a file present in the file system.
`MethodSource` ::
If the `URI` contains the `method` scheme and the fully qualified method name (FQMN) --
for example, `method:org.junit.Foo#bar(java.lang.String, java.lang.String[])`. Please
refer to the Javadoc for `{DiscoverySelectors}.{DiscoverySelectors_selectMethod}` for the
supported formats for a FQMN.
`ClassSource` ::
If the `URI` contains the `class` scheme and the fully qualified class name --
for example, `class:org.junit.Foo?line=42`.
`UriSource` ::
If none of the above `TestSource` implementations are applicable.
[[parallel-execution]]
== Parallel Execution
Dynamic tests and containers support
xref:writing-tests/parallel-execution.adoc[parallel execution]. You can configure their
`ExecutionMode` by using the `dynamicTest(Consumer)` and `dynamicContainer(Consumer)`
factory methods as illustrated by the following example.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/DynamicTestsDemo.java[tags=execution_mode]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/DynamicTestsDemo.kt[tags=execution_mode]
----
--
====
Executing the above test factory method results in the following test tree and execution
modes:
* dynamicTestsWithConfiguredExecutionMode() -- `CONCURRENT` (from `@Execution` annotation)
** Container A -- `CONCURRENT` (from `@Execution` annotation)
*** not null -- `SAME_THREAD` (from `executionMode(...)` call)
*** properties -- `CONCURRENT` (from `@Execution` annotation)
**** length > 0 -- `CONCURRENT` (from `executionMode(...)` call)
**** not empty -- `SAME_THREAD` (from `childExecutionMode(...)` call)
** ... (same for "Container B" and "Container C")
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/exception-handling.adoc
================================================
= Exception Handling
JUnit Jupiter provides robust support for handling test exceptions. This includes the
built-in mechanisms for managing test failures due to exceptions, the role of exceptions
in implementing assertions and assumptions, and how to specifically assert non-throwing
conditions in code.
[[uncaught]]
== Uncaught Exceptions
In JUnit Jupiter, if an exception is thrown from a test method, a lifecycle method, or an
extension and not caught within that test method, lifecycle method, or extension, the
framework will mark the test or test class as failed.
[TIP]
====
Failed assumptions deviate from this general rule.
In contrast to failed assertions, failed assumptions do not result in a test failure;
rather, a failed assumption results in a test being aborted.
See xref:writing-tests/assumptions.adoc[] for further details and examples.
====
In the following example, the `failsDueToUncaughtException()` method throws an
`ArithmeticException`. Since the exception is not caught within the test method, JUnit
Jupiter will mark the test as failed.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/exception/UncaughtExceptionHandlingDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/exception/UncaughtExceptionHandlingDemo.kt[tags=user_guide]
----
--
====
NOTE: It's important to note that specifying a `throws` clause in the test method has
no effect on the outcome of the test. JUnit Jupiter does not interpret a `throws` clause
as an expectation or assertion about what exceptions the test method should throw. A test
fails only if an exception is thrown unexpectedly or if an assertion fails.
[[failed-assertions]]
== Failed Assertions
Assertions in JUnit Jupiter are implemented using exceptions. The framework provides a set
of assertion methods in the `org.junit.jupiter.api.Assertions` class, which throw
`AssertionError` when an assertion fails. This mechanism is a core aspect of how JUnit
handles assertion failures as exceptions. See the xref:writing-tests/assertions.adoc[] section for
further information about JUnit Jupiter's assertion support.
NOTE: Third-party assertion libraries may choose to throw an `AssertionError` to signal a
failed assertion; however, they may also choose to throw different types of exceptions to
signal failures. See also: xref:writing-tests/assertions.adoc#third-party[Third-party Assertion Libraries].
TIP: JUnit Jupiter itself does not differentiate between failed assertions
(`AssertionError`) and other types of exceptions. All uncaught exceptions lead to a test
failure. However, Integrated Development Environments (IDEs) and other tools may
distinguish between these two types of failures by checking whether the thrown exception
is an instance of `AssertionError`.
In the following example, the `failsDueToUncaughtAssertionError()` method throws an
`AssertionError`. Since the exception is not caught within the test method, JUnit Jupiter
will mark the test as failed.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/exception/FailedAssertionDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/exception/FailedAssertionDemo.kt[tags=user_guide]
----
--
====
[[expected]]
== Asserting Expected Exceptions
JUnit Jupiter offers specialized assertions for testing that specific exceptions are
thrown under expected conditions. The `assertThrows()` and `assertThrowsExactly()`
assertions are critical tools for validating that your code responds correctly to error
conditions by throwing the appropriate exceptions.
[[expected-assertThrows]]
=== Using `assertThrows()`
The `assertThrows()` method is used to verify that a particular type of exception is
thrown during the execution of a provided executable block. It not only checks for the
type of the thrown exception but also its subclasses, making it suitable for more
generalized exception handling tests. The `assertThrows()` assertion method returns the
thrown exception object to allow performing additional assertions on it.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/exception/ExceptionAssertionDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/exception/ExceptionAssertionDemo.kt[tags=user_guide]
----
--
====
[[expected-assertThrowsExactly]]
=== Using `assertThrowsExactly()`
The `assertThrowsExactly()` method is used when you need to assert that the exception
thrown is exactly of a specific type, not allowing for subclasses of the expected
exception type. This is useful when precise exception handling behavior needs to be
validated. Similar to `assertThrows()`, the `assertThrowsExactly()` assertion method also
returns the thrown exception object to allow performing additional assertions on it.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/exception/ExceptionAssertionExactDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/exception/ExceptionAssertionExactDemo.kt[tags=user_guide]
----
--
====
[[not-expected]]
== Asserting That no Exception is Expected
Although any exception thrown from a test method will cause the test to fail, there are
certain use cases where it can be beneficial to explicitly assert that an exception is
_not_ thrown for a given code block within a test method. The `assertDoesNotThrow()`
assertion can be used when you want to verify that a particular piece of code does not
throw any exceptions.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/exception/AssertDoesNotThrowExceptionDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/exception/AssertDoesNotThrowExceptionDemo.kt[tags=user_guide]
----
--
====
NOTE: Third-party assertion libraries often provide similar support. For example, AssertJ
has `assertThatNoException().isThrownBy(() -> ...)`. See also:
xref:writing-tests/assertions.adoc#third-party[Third-party Assertion Libraries].
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/intro.adoc
================================================
= Writing Tests
The following example provides a glimpse at the minimum requirements for writing a test in
JUnit Jupiter. Subsequent sections of this chapter will provide further details on all
available features.
.A first test case
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/MyFirstJUnitJupiterTests.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/MyFirstJUnitJupiterTests.kt[tags=user_guide]
----
--
====
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/nested-tests.adoc
================================================
= Nested Tests
`@Nested` tests give the test writer more capabilities to express the relationship among
several groups of tests. Such nested tests make use of Java's nested classes and
facilitate hierarchical thinking about the test structure. Here's an elaborate example,
both as source code and as a screenshot of the execution within an IDE.
.Nested test suite for testing a stack
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/TestingAStackDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/TestingAStackDemo.kt[tags=user_guide]
----
--
====
When executing this example in an IDE, the test execution tree in the GUI will look
similar to the following image.
image::writing-tests_nested_test_ide.png[caption='',title='Executing a nested test in an IDE']
In this example, preconditions from outer tests are used in inner tests by defining
hierarchical lifecycle methods for the setup code. For example, `createNewStack()` is a
`@BeforeEach` lifecycle method that is used in the test class in which it is defined and
in all levels in the nesting tree below the class in which it is defined.
The fact that setup code from outer tests is run before inner tests are executed gives you
the ability to run all tests independently. You can even run inner tests alone without
running the outer tests, because the setup code from the outer tests is always executed.
NOTE: _Only non-static nested classes_ (i.e. _inner classes_) can serve as `@Nested` test
classes. Nesting can be arbitrarily deep, and those inner classes are subject to full
lifecycle support, including `@BeforeAll` and `@AfterAll` methods on each level.
[[interoperability]]
== Interoperability
`@Nested` may be combined with
xref:writing-tests/parameterized-classes-and-tests.adoc[`@ParameterizedClass`] in which case the nested test
class is parameterized.
The following example illustrates how to combine `@Nested` with `@ParameterizedClass` and
`@ParameterizedTest`.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/ParameterizedClassDemo.java[tags=nested]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/ParameterizedClassDemo.kt[tags=nested]
----
--
====
Executing the above test class yields the following output:
....
FruitTests ✔
├─ [1] fruit = "apple" ✔
│ └─ QuantityTests ✔
│ ├─ [1] quantity = 23 ✔
│ │ └─ test(Duration) ✔
│ │ ├─ [1] duration = "PT1H" ✔
│ │ └─ [2] duration = "PT2H" ✔
│ └─ [2] quantity = 42 ✔
│ └─ test(Duration) ✔
│ ├─ [1] duration = "PT1H" ✔
│ └─ [2] duration = "PT2H" ✔
└─ [2] fruit = "banana" ✔
└─ QuantityTests ✔
├─ [1] quantity = 23 ✔
│ └─ test(Duration) ✔
│ ├─ [1] duration = "PT1H" ✔
│ └─ [2] duration = "PT2H" ✔
└─ [2] quantity = 42 ✔
└─ test(Duration) ✔
├─ [1] duration = "PT1H" ✔
└─ [2] duration = "PT2H" ✔
....
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/parallel-execution.adoc
================================================
= Parallel Execution
By default, JUnit Jupiter tests are run sequentially in a single thread; however, running
tests in parallel -- for example, to speed up execution -- is available as an opt-in
feature. To enable parallel execution, set the `junit.jupiter.execution.parallel.enabled`
configuration parameter to `true` -- for example, in `junit-platform.properties` (see
xref:running-tests/configuration-parameters.adoc[] for other options).
Please note that enabling this property is only the first step required to execute tests
in parallel. If enabled, test classes and methods will still be executed sequentially by
default. Whether or not a node in the test tree is executed concurrently is controlled by
its execution mode. The following two modes are available.
`SAME_THREAD`::
Force execution in the same thread used by the parent. For example, when used on a test
method, the test method will be executed in the same thread as any `@BeforeAll` or
`@AfterAll` methods of the containing test class.
`CONCURRENT`::
Execute concurrently unless a resource lock forces execution in the same thread.
By default, nodes in the test tree use the `SAME_THREAD` execution mode. You can change
the default by setting the `junit.jupiter.execution.parallel.mode.default` configuration
parameter. Alternatively, you can use the `{Execution}` annotation to change the
execution mode for the annotated element and its subelements (if any) which allows you to
activate parallel execution for individual test classes, one by one.
[source,properties]
.Configuration parameters to execute all tests in parallel
----
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
----
The default execution mode is applied to all nodes of the test tree with a few notable
exceptions, namely test classes that use the `Lifecycle.PER_CLASS` mode or a
`{MethodOrderer}`. In the former case, test authors have to ensure that the test class is
thread-safe; in the latter, concurrent execution might conflict with the configured
execution order. Thus, in both cases, test methods in such test classes are only executed
concurrently if the `@Execution(CONCURRENT)` annotation is present on the test class or
method.
You can use the `@Execution` annotation to explicitly configure the execution mode for a
test class or method:
[source,java]
----
include::example$java/example/ExplicitExecutionModeDemo.java[tags=user_guide]
----
This allows test classes or methods to opt in or out of concurrent execution regardless of
the globally configured default.
When parallel execution is enabled and a default `{ClassOrderer}` is registered (see
xref:writing-tests/test-execution-order.adoc#classes[Class Order] for details), top-level test classes will
initially be sorted accordingly and scheduled in that order. However, they are not
guaranteed to be started in exactly that order since the threads they are executed on are
not controlled directly by JUnit.
All nodes of the test tree that are configured with the `CONCURRENT` execution mode will
be executed fully in parallel according to the provided
<> while observing the
declarative <>
mechanism. Please note that xref:running-tests/capturing-standard-output-error.adoc[] needs to be enabled
separately.
In addition, you can configure the default execution mode for top-level classes by setting
the `junit.jupiter.execution.parallel.mode.classes.default` configuration parameter. By
combining both configuration parameters, you can configure classes to run in parallel but
their methods in the same thread:
[source,properties]
.Configuration parameters to execute top-level classes in parallel but methods in same thread
----
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = same_thread
junit.jupiter.execution.parallel.mode.classes.default = concurrent
----
The opposite combination will run all methods within one class in parallel, but top-level
classes will run sequentially:
[source,properties]
.Configuration parameters to execute top-level classes sequentially but their methods in parallel
----
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = same_thread
----
The following diagram illustrates how the execution of two top-level test classes `A` and
`B` with two test methods per class behaves for all four combinations of
`junit.jupiter.execution.parallel.mode.default` and
`junit.jupiter.execution.parallel.mode.classes.default` (see labels in first column).
////
Source: https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNqFlE1u2zAQha9CEChio7IQKfVGXfUH_QEatICyKAIBwYQaW0QkUiDHhV3X2x4gvWFPUlKUbTmpEq2kN2-GHx403HKhS-QZn81mhSqlbWvYXDopY0I3LQgqVFcq1BIUuS_mnhIIP2jTALHvQYG1tL3ywgaJpLj7rAjND6hZsteoRvb39x9GlUEoLfvltMZL9_4M77EoSGrFJhYavAm-iA0-psH3Jia0lEymLANrk4idR_tjQintS2nEYOE4WLClwfP22H7b6QeP818MPWnvOcwJ_ldPAwutxMoYVPQ_XjHOKwa8YoT3tP0EUwww-_YHmEey52IV47EKH8dDhEAnBmmKR4mnvScdeNLnMJ8MU4yHKcQ45XiGgy4e8Qbdby1LtyNbby04VdhgwTP3qnBFBuqCR6EUdsSVtmFqwWtc0DcoS6mWXk_TebQv3YL5CK1Xk_ODuDSy_CIV5gRm2DiwuL5PKJdVd9DFUV9oRbn82aElc6_uogHxuzwP0DGBvbvCtcs17tO-6vZyy_yI2QIaWW8ydva1RcVyUPbsdahYNz1L5u2a7VjsSVnst5yRG-a6--sjU1rhqSNTVM1EJetykqqXyfSRueCF2rmwYUU63yjBMzIrjPiq9XfNewlLAw3PFlBbp2IpSZvLcHN1F1jEW1DXWu89u3-YPX1X
---
displayMode: compact
---
gantt
dateFormat X
axisFormat %s
tickInterval 1
title ↓ threads | time →
section (same_thread, same_thread)
A.test1() :ass1, 0, 1
A.test2() :ass2, after ass1, 2
B.test1() :bss1, after ass2, 3
B.test2() :bss2, after bss1, 4
section (same_thread, concurrent)
A.test1() :asc1, 0, 1
A.test2() :asc2, after asc1, 2
B.test1() :bsc1, 0, 1
B.test2() :bsc2, after bsc1, 2
section (concurrent, same_thread)
A.test1() :acs1, 0, 1
A.test2() :acs2, 0, 1
B.test1() :bcs1, after acs1, 2
B.test2() :bcs2, after acs2, 2
section (concurrent, concurrent)
A.test1() :acc1, 0, 1
A.test2() :acc2, 0, 1
B.test1() :bcc1, 0, 1
B.test2() :bcc2, 0, 1
////
image::writing-tests_execution_mode.svg[caption='',title='Default execution mode configuration combinations']
If the `junit.jupiter.execution.parallel.mode.classes.default` configuration parameter is
not explicitly set, the value for `junit.jupiter.execution.parallel.mode.default` will be
used instead.
[[config]]
== Configuration
[[config-executor-service]]
=== Executor Service
If parallel execution is enabled, a thread pool is used behind the scenes to execute tests
concurrently. You can configure which implementation of `HierarchicalTestExecutorService`
is used be setting the `junit.jupiter.execution.parallel.config.executor-service`
configuration parameter to one of the following options:
`fork_join_pool` (default)::
Use an executor service that is backed by a `ForkJoinPool` from the JDK. This will cause
tests to be executed in a `ForkJoinWorkerThread`. In some cases, usages of
`ForkJoinPool` in test or production code or calls to blocking JDK APIs may cause the
number of concurrently executing tests to increase. To avoid this situation, please use
`worker_thread_pool`.
`worker_thread_pool` (experimental)::
Use an executor service that is backed by a regular thread pool and does not create
additional threads if test or production code uses `ForkJoinPool` or calls a blocking
API in the JDK.
WARNING: Using `worker_thread_pool` is currently an _experimental_ feature. You're invited
to give it a try and provide feedback to the JUnit team so they can improve and eventually
xref:api-evolution.adoc[promote] this feature.
[[config-strategies]]
=== Strategies
Properties such as the desired parallelism and the maximum pool size can be configured
using a `{ParallelExecutionConfigurationStrategy}`. The JUnit Platform provides two
implementations out of the box: `dynamic` and `fixed`. Alternatively, you may implement a
`custom` strategy.
To select a strategy, set the `junit.jupiter.execution.parallel.config.strategy`
configuration parameter to one of the following options.
`dynamic`::
Computes the desired parallelism based on the number of available processors/cores
multiplied by the `junit.jupiter.execution.parallel.config.dynamic.factor`
configuration parameter (defaults to `1`).
The optional `junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor`
configuration parameter can be used to limit the maximum number of threads.
`fixed`::
Uses the mandatory `junit.jupiter.execution.parallel.config.fixed.parallelism`
configuration parameter as the desired parallelism.
The optional `junit.jupiter.execution.parallel.config.fixed.max-pool-size`
configuration parameter can be used to limit the maximum number of threads.
`custom`::
Allows you to specify a custom `{ParallelExecutionConfigurationStrategy}`
implementation via the mandatory `junit.jupiter.execution.parallel.config.custom.class`
configuration parameter to determine the desired configuration.
If no configuration strategy is set, JUnit Jupiter uses the `dynamic` configuration
strategy with a factor of `1`. Consequently, the desired parallelism will be equal to the
number of available processors/cores.
.Parallelism alone does not imply maximum number of concurrent threads
NOTE: By default, JUnit Jupiter does not guarantee that the number of threads used to
execute test will not exceed the configured parallelism. For example, when using one
of the synchronization mechanisms described in the next section, the executor service
implementation may spawn additional threads to ensure execution continues with sufficient
parallelism. If you require such guarantees, it is possible to limit the maximum number of
threads by configuring the maximum pool size of the `dynamic`, `fixed` and `custom`
strategies.
[[config-properties]]
=== Relevant properties
The following table lists relevant properties for configuring parallel execution. See
xref:running-tests/configuration-parameters.adoc[] for details on how to set such properties.
==== General
`junit.jupiter.execution.parallel.enabled=true|false`::
Enable/disable parallel test execution (defaults to `false`).
`junit.jupiter.execution.parallel.mode.default=concurrent|same_thread`::
Default execution mode of nodes in the test tree (defaults to `same_thread`).
`junit.jupiter.execution.parallel.mode.classes.default=concurrent|same_thread`::
Default execution mode of top-level classes (defaults to `same_thread`).
`junit.jupiter.execution.parallel.config.executor-service=fork_join_pool|worker_thread_pool`::
Type of `HierarchicalTestExecutorService` to use for parallel execution (defaults to
`fork_join_pool`).
`junit.jupiter.execution.parallel.config.strategy=dynamic|fixed|custom`::
Execution strategy for desired parallelism, maximum pool size, etc. (defaults to `dynamic`).
==== Dynamic strategy
`junit.jupiter.execution.parallel.config.dynamic.factor=decimal`::
Factor to be multiplied by the number of available processors/cores to determine the
desired parallelism for the ```dynamic``` configuration strategy.
Must be a positive decimal number (defaults to `1.0`).
`junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor=decimal`::
Factor to be multiplied by the number of available processors/cores and the value of
`junit.jupiter.execution.parallel.config.dynamic.factor` to determine the desired
parallelism for the ```dynamic``` configuration strategy.
Must be a positive decimal number greater than or equal to `1.0` (defaults to 256 plus
the value of `junit.jupiter.execution.parallel.config.dynamic.factor` multiplied by the
number of available processors/cores)
`junit.jupiter.execution.parallel.config.dynamic.saturate=true|false`::
Enable/disable saturation of the underlying `ForkJoinPool` for the ```dynamic```
configuration strategy (defaults to `true`). Only used if
`junit.jupiter.execution.parallel.config.executor-service` is set to `fork_join_pool`.
==== Fixed strategy
`junit.jupiter.execution.parallel.config.fixed.parallelism=integer`::
Desired parallelism for the ```fixed``` configuration strategy (no default value). Must
be a positive integer.
`junit.jupiter.execution.parallel.config.fixed.max-pool-size=integer`::
Desired maximum pool size of the underlying fork-join pool for the ```fixed```
configuration strategy. Must be a positive integer greater than or equal to
`junit.jupiter.execution.parallel.config.fixed.parallelism` (defaults to 256 plus the
value of `junit.jupiter.execution.parallel.config.fixed.parallelism`).
`junit.jupiter.execution.parallel.config.fixed.saturate=true|false`::
Enable/disable saturation of the underlying `ForkJoinPool` for the ```fixed```
configuration strategy (defaults to `true`). Only used if
`junit.jupiter.execution.parallel.config.executor-service` is set to `fork_join_pool`.
==== Custom strategy
`junit.jupiter.execution.parallel.config.custom.class=classname`::
Fully qualified class name of the `ParallelExecutionConfigurationStrategy` to be used
for the ```custom``` configuration strategy (no default value).
[[synchronization]]
== Synchronization
In addition to controlling the execution mode using the `{Execution}` annotation, JUnit
Jupiter provides another annotation-based declarative synchronization mechanism. The
`{ResourceLock}` annotation allows you to declare that a test class or method uses a
specific shared resource that requires synchronized access to ensure reliable test
execution. The shared resource is identified by a unique name which is a `String`. The
name can be user-defined or one of the predefined constants in `{Resources}`:
`SYSTEM_PROPERTIES`, `SYSTEM_OUT`, `SYSTEM_ERR`, `LOCALE`, or `TIME_ZONE`.
In addition to declaring these shared resources statically, the `{ResourceLock}`
annotation has a `providers` attribute that allows registering implementations of the
`{ResourceLocksProvider}` interface that can add shared resources dynamically at runtime.
Note that resources declared statically with `{ResourceLock}` annotation are combined with
resources added dynamically by `{ResourceLocksProvider}` implementations.
If the tests in the following example were run in parallel _without_ the use of
`{ResourceLock}`, they would be _flaky_. Sometimes they would pass, and at other times they
would fail due to the inherent race condition of writing and then reading the same JVM
System Property.
When access to shared resources is declared using the `{ResourceLock}` annotation, the
JUnit Jupiter engine uses this information to ensure that no conflicting tests are run in
parallel. This guarantee extends to lifecycle methods of a test class or method. For
example, if a test method is annotated with a `{ResourceLock}` annotation, the "lock" will
be acquired before any `@BeforeEach` methods are executed and released after all
`@AfterEach` methods have been executed.
[NOTE]
.Running tests in isolation
====
If most of your test classes can be run in parallel without any synchronization but you
have some test classes that need to run in isolation, you can mark the latter with the
`{Isolated}` annotation. Tests in such classes are executed sequentially without any other
tests running at the same time.
====
In addition to the `String` that uniquely identifies the shared resource, you may specify
an access mode. Two tests that require `READ` access to a shared resource may run in
parallel with each other but not while any other test that requires `READ_WRITE` access
to the same shared resource is running.
[source,java]
.Declaring shared resources "statically" with `{ResourceLock}` annotation
----
include::example$java/example/sharedresources/StaticSharedResourcesDemo.java[tags=user_guide]
----
[source,java]
.Adding shared resources "dynamically" with `{ResourceLocksProvider}` implementation
----
include::example$java/example/sharedresources/DynamicSharedResourcesDemo.java[tags=user_guide]
----
Also, "static" shared resources can be declared for _direct_ child nodes via the `target`
attribute in the `{ResourceLock}` annotation, the attribute accepts a value from
the `{ResourceLockTarget}` enum.
Specifying `target = CHILDREN` in a class-level `{ResourceLock}` annotation
has the same semantics as adding an annotation with the same `value` and `mode`
to each test method and nested test class declared in this class.
This may improve parallelization when a test class declares a `READ` lock,
but only a few methods hold a `READ_WRITE` lock.
Tests in the following example would run in the `SAME_THREAD` if the `{ResourceLock}`
didn't have `target = CHILDREN`. This is because the test class declares a `READ`
shared resource, but one test method holds a `READ_WRITE` lock,
which would force the `SAME_THREAD` execution mode for all the test methods.
[source,java]
.Declaring shared resources for child nodes with `target` attribute
----
include::example$java/example/sharedresources/ChildrenSharedResourcesDemo.java[tags=user_guide]
----
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/parameterized-classes-and-tests.adoc
================================================
= Parameterized Classes and Tests
_Parameterized tests_ make it possible to run a test method multiple times with different
arguments. They are declared just like regular `@Test` methods but use the
`{ParameterizedTest}` annotation instead.
_Parameterized classes_ make it possible to run _all_ tests in a test class, including
xref:writing-tests/nested-tests.adoc[], multiple times with different arguments. They are declared just
like regular test classes and may contain any supported test method type (including
`@ParameterizedTest`) but annotated with the `{ParameterizedClass}` annotation.
WARNING: _Parameterized classes_ are currently an _experimental_ feature. You're invited
to give it a try and provide feedback to the JUnit team so they can improve and eventually
xref:api-evolution.adoc[promote] this feature.
Regardless of whether you are parameterizing a test method or a test class, you must
declare at least one <> that will
provide the arguments for each invocation and then
<> the arguments in the
parameterized method or class, respectively.
The following example demonstrates a parameterized test that uses the `@ValueSource`
annotation to specify a `String` array as the source of arguments.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=first_example]
----
When executing the above parameterized test method, each invocation will be reported
separately. For instance, the `ConsoleLauncher` will print output similar to the
following.
....
palindromes(String) ✔
├─ [1] candidate = "racecar" ✔
├─ [2] candidate = "radar" ✔
└─ [3] candidate = "able was I ere I saw elba" ✔
....
The same `@ValueSource` annotation can be used to specify the source of arguments for a
`@ParameterizedClass`.
[source,java,indent=0]
----
include::example$java/example/ParameterizedClassDemo.java[tags=first_example]
----
When executing the above parameterized test class, each invocation will be reported
separately. For instance, the `ConsoleLauncher` will print output similar to the
following.
....
PalindromeTests ✔
├─ [1] candidate = "racecar" ✔
│ ├─ palindrome() ✔
│ └─ reversePalindrome() ✔
├─ [2] candidate = "radar" ✔
│ ├─ palindrome() ✔
│ └─ reversePalindrome() ✔
└─ [3] candidate = "able was I ere I saw elba" ✔
├─ palindrome() ✔
└─ reversePalindrome() ✔
....
[[setup]]
== Required Setup
In order to use parameterized classes or tests you need to add a dependency on the
`junit-jupiter-params` artifact. Please refer to xref:appendix.adoc#dependency-metadata[Dependency Metadata] for details.
[[consuming-arguments]]
== Consuming Arguments
[[consuming-arguments-methods]]
=== Parameterized Tests
Parameterized test methods _consume_ arguments directly from the configured source (see
<>) following a one-to-one correlation between argument source index and method
parameter index (see examples in <>). However, a parameterized test
method may also choose to _aggregate_ arguments from the source into a single object
passed to the method (see <>). Additional arguments may also be
provided by a `ParameterResolver` — for example, to obtain an instance of `TestInfo`,
`TestReporter`, and so forth. Specifically, a parameterized test method must declare
formal parameters according to the following rules.
* Zero or more _indexed parameters_ must be declared first.
* Zero or more _aggregators_ must be declared next.
* Zero or more arguments supplied by a `ParameterResolver` must be declared last.
In this context, an _indexed parameter_ is an argument for a given index in the
`{Arguments}` provided by an `{ArgumentsProvider}` that is passed as an argument to the
parameterized method at the same index in the method's formal parameter list. An
_aggregator_ is any parameter of type `{ArgumentsAccessor}` or any parameter annotated
with `{AggregateWith}`.
[[consuming-arguments-classes]]
=== Parameterized Classes
Parameterized classes _consume_ arguments directly from the configured source (see
<>); either via their unique constructor or via
field injection. If a `{Parameter}`-annotated field is declared in the parameterized class
or one of its superclasses, field injection will be used. Otherwise, constructor injection
will be used.
[[consuming-arguments-constructor-injection]]
==== Constructor Injection
WARNING: Constructor injection can only be used with the (default) `PER_METHOD`
xref:writing-tests/test-instance-lifecycle.adoc[test instance lifecycle] mode. Please use
<>
with the `PER_CLASS` mode instead.
For constructor injection, the same rules apply as defined for
<>
above. In the following example, two arguments are injected into the constructor of the
test class.
[source,java,indent=0]
----
include::example$java/example/ParameterizedClassDemo.java[tags=constructor_injection]
----
You may use _records_ to implement parameterized classes that avoid the boilerplate code
of declaring a test class constructor.
[source,java,indent=0]
----
include::example$java/example/ParameterizedRecordDemo.java[tags=example]
----
[[consuming-arguments-field-injection]]
==== Field Injection
For field injection, the following rules apply for fields annotated with `@Parameter`.
* Zero or more _indexed parameters_ may be declared; each must have a unique index
specified in its `@Parameter(index)` annotation. The index may be omitted if there is
only one indexed parameter. If there are at least two indexed parameter declarations,
there must be declarations for all indexes from 0 to the largest declared index.
* Zero or more _aggregators_ may be declared; each without specifying an index in its
`@Parameter` annotation.
* Zero or more other fields may be declared as usual as long as they're not annotated with
`@Parameter`.
In this context, an _indexed parameter_ is an argument for a given index in the
`{Arguments}` provided by an `{ArgumentsProvider}` that is injected into a field annotated
with `@Parameter(index)`. An _aggregator_ is any `@Parameter`-annotated field of type
{ArgumentsAccessor} or any field annotated with {AggregateWith}.
The following example demonstrates how to use field injection to consume multiple
arguments in a parameterized class.
[source,java,indent=0]
----
include::example$java/example/ParameterizedClassDemo.java[tags=field_injection]
----
If field injection is used, no constructor parameters will be resolved with arguments from
the source. Other xref:writing-tests/dependency-injection-for-constructors-and-methods.adoc[`ParameterResolver` extensions]
may resolve constructor parameters as usual, though.
[[consuming-arguments-lifecycle-method]]
==== Lifecycle Methods
`{BeforeParameterizedClassInvocation}` and `{AfterParameterizedClassInvocation}` can also
be used to consume arguments if their `injectArguments` attribute is set to `true` (the
default). If so, their method signatures must follow the same rules apply as defined for
<> and
additionally use the same parameter types as the _indexed parameters_ of the parameterized
test class. Please refer to the Javadoc of `{BeforeParameterizedClassInvocation}` and
`{AfterParameterizedClassInvocation}` for details and to the
<> section for an
example.
[NOTE]
.AutoCloseable arguments
====
Arguments that implement `java.lang.AutoCloseable` (or `java.io.Closeable` which extends
`java.lang.AutoCloseable`) will be automatically closed after the parameterized class or
test invocation.
To prevent this from happening, set the `autoCloseArguments` attribute in
`@ParameterizedTest` to `false`. Specifically, if an argument that implements
`AutoCloseable` is reused for multiple invocations of the same parameterized class or test
method, you must specify the `autoCloseArguments = false` on the `{ParameterizedClass}` or
`{ParameterizedTest}` annotation to ensure that the argument is not closed between
invocations.
====
[[consuming-arguments-other-extensions]]
=== Other Extensions
Other extensions can access the parameters and resolved arguments of a parameterized test
or class by retrieving a `{ParameterInfo}` object from the `{ExtensionContext_Store}`.
Please refer to the Javadoc of `{ParameterInfo}` for details.
[[sources]]
== Sources of Arguments
Out of the box, JUnit Jupiter provides quite a few _source_ annotations. Each of the
following subsections provides a brief overview and an example for each of them. Please
refer to the Javadoc in the `{params-provider-package}` package for additional
information.
TIP: All source annotations in this section are applicable to both `{ParameterizedClass}`
and `{ParameterizedTest}`. For the sake of brevity, the examples in this section will only
show how to use them with `{ParameterizedTest}` methods.
[[sources-ValueSource]]
=== @ValueSource
`@ValueSource` is one of the simplest possible sources. It lets you specify a single
array of literal values and can only be used for providing a single argument per
parameterized test invocation.
The following types of literal values are supported by `@ValueSource`.
- `short`
- `byte`
- `int`
- `long`
- `float`
- `double`
- `char`
- `boolean`
- `java.lang.String`
- `java.lang.Class`
For example, the following `@ParameterizedTest` method will be invoked three times, with
the values `1`, `2`, and `3` respectively.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=ValueSource_example]
----
[[sources-null-and-empty]]
=== Null and Empty Sources
In order to check corner cases and verify proper behavior of our software when it is
supplied _bad input_, it can be useful to have `null` and _empty_ values supplied to our
parameterized tests. The following annotations serve as sources of `null` and empty values
for parameterized tests that accept a single argument.
* `{NullSource}`: provides a single `null` argument to the annotated `@ParameterizedClass`
or `@ParameterizedTest`.
- `@NullSource` cannot be used for a parameter that has a primitive type.
* `{EmptySource}`: provides a single _empty_ argument to the annotated
`@ParameterizedClass` or `@ParameterizedTest` for parameters of the following types:
`java.lang.String`, `java.lang.Iterable`, `java.util.Iterator`, `java.util.ListIterator`
`java.util.Collection` (and concrete subtypes with a `public` no-arg constructor),
`java.util.List`, `java.util.Set`, `java.util.SortedSet`, `java.util.NavigableSet`,
`java.util.Map` (and concrete subtypes with a `public` no-arg constructor),
`java.util.SortedMap`, `java.util.NavigableMap`, primitive arrays (for example, `int[]`,
`char[][]`, etc.), object arrays (for example, `String[]`, `Integer[][]`, etc.).
* `{NullAndEmptySource}`: a _composed annotation_ that combines the functionality of
`@NullSource` and `@EmptySource`.
If you need to supply multiple varying types of _blank_ strings to a parameterized
class or test, you can achieve that using
<> -- for example,
`@ValueSource(strings = {"{nbsp}", "{nbsp}{nbsp}{nbsp}", "\t", "\n"})`.
You can also combine `@NullSource`, `@EmptySource`, and `@ValueSource` to test a wider
range of `null`, _empty_, and _blank_ input. The following example demonstrates how to
achieve this for strings.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=NullAndEmptySource_example1]
----
Making use of the composed `@NullAndEmptySource` annotation simplifies the above as
follows.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=NullAndEmptySource_example2]
----
NOTE: Both variants of the `nullEmptyAndBlankStrings(String)` parameterized test method
result in six invocations: 1 for `null`, 1 for the empty string, and 4 for the explicit
blank strings supplied via `@ValueSource`.
[[sources-EnumSource]]
=== @EnumSource
`@EnumSource` provides a convenient way to use `Enum` constants.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_example]
----
The annotation's `value` attribute is optional. When omitted, the declared type of the
first parameter is used. The test will fail if it does not reference an enum type.
Thus, the `value` attribute is required in the above example because the method parameter
is declared as `TemporalUnit`, i.e. the interface implemented by `ChronoUnit`, which isn't
an enum type. Changing the method parameter type to `ChronoUnit` allows you to omit the
explicit enum type from the annotation as follows.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_example_autodetection]
----
The annotation provides an optional `names` attribute that lets you specify which
constants shall be used, like in the following example.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_include_example]
----
In addition to `names`, you can use the `from` and `to` attributes to specify a range of
constants. The range starts from the constant specified in the `from` attribute and
includes all subsequent constants up to and including the one specified in the `to`
attribute, based on the natural order of the enum constants.
If `from` and `to` attributes are omitted, they default to the first and last constants
in the enum type, respectively. If all `names`, `from`, and `to` attributes are omitted,
all constants will be used. The following example demonstrates how to specify a range of
constants.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_range_example]
----
The `@EnumSource` annotation also provides an optional `mode` attribute that enables
fine-grained control over which constants are passed to the test method. For example, you
can exclude names from the enum constant pool or specify regular expressions as in the
following examples.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_exclude_example]
----
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_regex_example]
----
You can also combine `mode` with the `from`, `to` and `names` attributes to define a
range of constants while excluding specific values from that range as shown below.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_range_exclude_example]
----
[[sources-MethodSource]]
=== @MethodSource
`{MethodSource}` allows you to refer to one or more _factory_ methods of the test class
or external classes.
Factory methods within the test class must be `static` unless the test class is annotated
with `@TestInstance(Lifecycle.PER_CLASS)`; whereas, factory methods in external classes
must always be `static`.
Each factory method must generate a _stream_ of _arguments_, and each set of arguments
within the stream will be provided as the physical arguments for individual invocations of
the annotated `@ParameterizedClass` or `@ParameterizedTest`. Generally speaking this
translates to a `Stream` of `Arguments` (i.e., `Stream`); however, the actual
concrete return type can take on many forms. In this context, a "stream" is anything that
JUnit can reliably convert into a `Stream`, such as `Stream`, `DoubleStream`,
`LongStream`, `IntStream`, `Collection`, `Iterator`, `Iterable`, an array of objects or
primitives, or any type that provides an `iterator(): Iterator` method (such as, for
example, a `kotlin.sequences.Sequence`). The "arguments" within the stream can be supplied
as an instance of `Arguments`, an array of objects (for example, `Object[]`), or a single
value if the parameterized class or test method accepts a single argument.
If the return type is `Stream` or one of the primitive streams,
JUnit will properly close it by calling `BaseStream.close()`,
making it safe to use a resource such as `Files.lines()`.
If you only need a single parameter, you can return a `Stream` of instances of the
parameter type as demonstrated in the following example.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=simple_MethodSource_example]
----
For a `@ParameterizedClass`, providing a factory method name via `@MethodSource` is
mandatory. For a `@ParameterizedTest`, if you do not explicitly provide a factory method
name, JUnit Jupiter will search for a _factory_ method with the same name as the current
`@ParameterizedTest` method by convention. This is demonstrated in the following example.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=simple_MethodSource_without_value_example]
----
Streams for primitive types (`DoubleStream`, `IntStream`, and `LongStream`) are also
supported as demonstrated by the following example.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=primitive_MethodSource_example]
----
If a parameterized class or test method declares multiple parameters, you need to return a
collection, stream, or array of `Arguments` instances or object arrays as shown below (see
the Javadoc for `{MethodSource}` for further details on supported return types). Note that
`arguments(Object...)` is a static factory method defined in the `Arguments` interface. In
addition, `Arguments.of(Object...)` may be used as an alternative to
`arguments(Object...)`.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=multi_arg_MethodSource_example]
----
An external, `static` _factory_ method can be referenced by providing its _fully qualified
method name_ as demonstrated in the following example.
[source,java,indent=0]
----
package example;
include::example$java/example/ExternalMethodSourceDemo.java[tags=external_MethodSource_example]
----
Factory methods can declare parameters, which will be provided by registered
implementations of the `ParameterResolver` extension API. In the following example, the
factory method is referenced by its name since there is only one such method in the test
class. If there are several local methods with the same name, parameters can also be
provided to differentiate them – for example, `@MethodSource("factoryMethod()")` or
`@MethodSource("factoryMethod(java.lang.String)")`. Alternatively, the factory method
can be referenced by its fully qualified method name — for example,
`@MethodSource("example.MyTests#factoryMethod(java.lang.String)")`.
[source,java,indent=0]
----
include::example$java/example/MethodSourceParameterResolutionDemo.java[tags=parameter_resolution_MethodSource_example]
----
[[sources-FieldSource]]
=== @FieldSource
`{FieldSource}` allows you to refer to one or more fields of the test class or external
classes.
Fields within the test class must be `static` unless the test class is annotated with
`@TestInstance(Lifecycle.PER_CLASS)`; whereas, fields in external classes must always be
`static`.
Each field must be able to supply a _stream_ of arguments, and each set of "arguments"
within the "stream" will be provided as the physical arguments for individual invocations
of the annotated `@ParameterizedClass` or `@ParameterizedTest`.
In this context, a "stream" is anything that JUnit can reliably convert to a `Stream`;
however, the actual concrete field type can take on many forms. Generally speaking this
translates to a `Collection`, an `Iterable`, a `Supplier` of a stream (`Stream`,
`DoubleStream`, `LongStream`, or `IntStream`), a `Supplier` of an `Iterator`, an array of
objects or primitives, or any type that provides an `iterator(): Iterator` method (such
as, for example, a `kotlin.sequences.Sequence`). Each set of "arguments" within the
"stream" can be supplied as an instance of `Arguments`, an array of objects (for example,
`Object[]`, `String[]`, etc.), or a single value if the parameterized class or test method accepts
a single argument.
[WARNING]
====
In contrast to the supported return types for
<> factory
methods, the value of a `@FieldSource` field cannot be an instance of `Stream`,
`DoubleStream`, `LongStream`, `IntStream`, or `Iterator`, since the values of such types
are _consumed_ the first time they are processed. However, if you wish to use one of
these types, you can wrap it in a `Supplier` — for example, `Supplier`.
====
If the `Supplier` return type is `Stream` or one of the primitive streams,
JUnit will properly close it by calling `BaseStream.close()`,
making it safe to use a resource such as `Files.lines()`.
Please note that a one-dimensional array of objects supplied as a set of "arguments" will
be handled differently than other types of arguments. Specifically, all the elements of a
one-dimensional array of objects will be passed as individual physical arguments to the
`@ParameterizedClass` or `@ParameterizedTest`. See the Javadoc for `{FieldSource}` for
further details.
For a `@ParameterizedClass`, providing a field name via `@FieldSource` is mandatory. For a
`@ParameterizedTest`, if you do not explicitly provide a field name, JUnit Jupiter will
search in the test class for a field that has the same name as the current
`@ParameterizedTest` method by convention. This is demonstrated in the following example.
This parameterized test method will be invoked twice: with the values `"apple"` and
`"banana"`.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=default_field_FieldSource_example]
----
The following example demonstrates how to provide a single explicit field name via
`@FieldSource`. This parameterized test method will be invoked twice: with the values
`"apple"` and `"banana"`.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=explicit_field_FieldSource_example]
----
The following example demonstrates how to provide multiple explicit field names via
`@FieldSource`. This example uses the `listOfFruits` field from the previous example as
well as the `additionalFruits` field. Consequently, this parameterized test method will
be invoked four times: with the values `"apple"`, `"banana"`, `"cherry"`, and
`"dewberry"`.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=multiple_fields_FieldSource_example]
----
It is also possible to provide a `Stream`, `DoubleStream`, `IntStream`, `LongStream`, or
`Iterator` as the source of arguments via a `@FieldSource` field as long as the stream or
iterator is wrapped in a `java.util.function.Supplier`. The following example demonstrates
how to provide a `Supplier` of a `Stream` of named arguments. This parameterized test
method will be invoked twice: with the values `"apple"` and `"banana"` and with display
names `"Apple"` and `"Banana"`, respectively.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=named_arguments_FieldSource_example]
----
[NOTE]
====
Note that `arguments(Object...)` is a static factory method defined in the
`org.junit.jupiter.params.provider.Arguments` interface.
Similarly, `named(String, Object)` is a static factory method defined in the
`org.junit.jupiter.api.Named` interface.
====
If a parameterized class or test method declares multiple parameters, the corresponding
`@FieldSource` field must be able to provide a collection, stream supplier, or array of
`Arguments` instances or object arrays as shown below (see the Javadoc for `{FieldSource}`
for further details on supported types).
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=multi_arg_FieldSource_example]
----
[NOTE]
====
Note that `arguments(Object...)` is a static factory method defined in the
`org.junit.jupiter.params.provider.Arguments` interface.
====
An external, `static` `@FieldSource` field can be referenced by providing its
_fully qualified field name_ as demonstrated in the following example.
[source,java,indent=0]
----
include::example$java/example/ExternalFieldSourceDemo.java[tags=external_field_FieldSource_example]
----
[[sources-CsvSource]]
=== @CsvSource
`@CsvSource` allows you to express argument lists as comma-separated values (i.e., CSV
`String` literals). Each string provided via the `value` attribute in `@CsvSource`
represents a CSV record and results in one invocation of the parameterized class or
test. The first record may optionally be used to supply CSV headers (see the Javadoc for
the `useHeadersInDisplayName` attribute for details and an example).
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=CsvSource_example]
----
The default delimiter is a comma (`,`), but you can use another character by setting the
`delimiter` attribute. Alternatively, the `delimiterString` attribute allows you to use a
`String` delimiter instead of a single character. However, both delimiter attributes
cannot be set simultaneously.
By default, `@CsvSource` uses a single quote (`'`) as its quote character, but this can be
changed via the `quoteCharacter` attribute. See the `'lemon, lime'` value in the example
above and in the table below. An empty, quoted value (`''`) results in an empty `String`
unless the `emptyValue` attribute is set; whereas, an entirely _empty_ value is
interpreted as a `null` reference. By specifying one or more `nullValues`, a custom value
can be interpreted as a `null` reference (see the `NIL` example in the table below). An
`ArgumentConversionException` is thrown if the target type of a `null` reference is a
primitive type.
NOTE: An _unquoted_ empty value will always be converted to a `null` reference regardless
of any custom values configured via the `nullValues` attribute.
Except within a quoted string, leading and trailing whitespace in a CSV column is trimmed
by default. This behavior can be changed by setting the
`ignoreLeadingAndTrailingWhitespace` attribute to `true`.
[cols="50,50"]
|===
| Example Input | Resulting Argument List
| `@CsvSource({ "apple, banana" })` | `"apple"`, `"banana"`
| `@CsvSource({ "apple, 'lemon, lime'" })` | `"apple"`, `"lemon, lime"`
| `@CsvSource({ "apple, ''" })` | `"apple"`, `""`
| `@CsvSource({ "apple, " })` | `"apple"`, `null`
| `@CsvSource(value = { "apple, banana, NIL" }, nullValues = "NIL")` | `"apple"`, `"banana"`, `null`
| `@CsvSource(value = { " apple , banana" }, ignoreLeadingAndTrailingWhitespace = false)` | `" apple "`, `" banana"`
|===
If the programming language you are using supports Java _text blocks_ or equivalent
multi-line string literals, you can alternatively use the `textBlock` attribute of
`@CsvSource`. Each record within a text block represents a CSV record and results in one
invocation of the parameterized class or test. The first record may optionally be used to
supply CSV headers by setting the `useHeadersInDisplayName` attribute to `true` as in the
example below.
Using a text block, the previous example can be implemented as follows.
[source,java,indent=0]
----
@ParameterizedTest
@CsvSource(useHeadersInDisplayName = true, textBlock = """
FRUIT, RANK
apple, 1
banana, 2
'lemon, lime', 0xF1
strawberry, 700_000
""")
void testWithCsvSource(String fruit, int rank) {
// ...
}
----
The generated display names for the previous example include the CSV header names.
----
[1] FRUIT = "apple", RANK = "1"
[2] FRUIT = "banana", RANK = "2"
[3] FRUIT = "lemon, lime", RANK = "0xF1"
[4] FRUIT = "strawberry", RANK = "700_000"
----
In contrast to CSV records supplied via the `value` attribute, a text block can contain
comments. Any line beginning with the value of the `commentCharacter` attribute (`+++#+++`
by default) will be treated as a comment and ignored. Note that there is one exception
to this rule: if the comment character appears within a quoted field, it loses
its special meaning.
The comment character must be the first character on the line without any leading
whitespace. It is therefore recommended that the closing text block delimiter (`"""`)
be placed either at the end of the last line of input or on the following line,
left aligned with the rest of the input (as can be seen in the example below which
demonstrates formatting similar to a table).
[source,java,indent=0]
----
@ParameterizedTest
@CsvSource(delimiter = '|', quoteCharacter = '"', textBlock = """
#-----------------------------
# FRUIT | RANK
#-----------------------------
apple | 1
#-----------------------------
banana | 2
#-----------------------------
"lemon lime" | 0xF1
#-----------------------------
strawberry | 700_000
#-----------------------------
""")
void testWithCsvSource(String fruit, int rank) {
// ...
}
----
[NOTE]
====
Java's https://docs.oracle.com/en/java/javase/17/text-blocks/index.html[text block]
feature automatically removes _incidental whitespace_ when the code is compiled.
However other JVM languages such as Groovy and Kotlin do not. Thus, if you are using a
programming language other than Java and your text block contains comments or new lines
within quoted strings, you will need to ensure that there is no leading whitespace within
your text block.
====
[[sources-CsvFileSource]]
=== @CsvFileSource
`@CsvFileSource` lets you use comma-separated value (CSV) files from the classpath or the
local file system. Each record from a CSV file results in one invocation of the
parameterized class or test. The first record may optionally be used to supply CSV
headers. You can instruct JUnit to ignore the headers via the `numLinesToSkip` attribute.
If you would like for the headers to be used in the display names, you can set the
`useHeadersInDisplayName` attribute to `true`. The examples below demonstrate the use of
`numLinesToSkip` and `useHeadersInDisplayName`.
The default delimiter is a comma (`,`), but you can use another character by setting the
`delimiter` attribute. Alternatively, the `delimiterString` attribute allows you to use a
`String` delimiter instead of a single character. However, both delimiter attributes
cannot be set simultaneously.
.Comments in CSV files
NOTE: Any line beginning with the value of the `commentCharacter` attribute (`+++#+++`
by default) will be interpreted as a comment and will be ignored.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=CsvFileSource_example]
----
[source,csv,indent=0]
.two-column.csv
----
include::example$resources/two-column.csv[]
----
The following listing shows the generated display names for the first two parameterized
test methods above.
----
[1] country = "Sweden", reference = "1"
[2] country = "Poland", reference = "2"
[3] country = "United States of America", reference = "3"
[4] country = "France", reference = "700_000"
----
The following listing shows the generated display names for the last parameterized test
method above that uses CSV header names.
----
[1] COUNTRY = "Sweden", REFERENCE = "1"
[2] COUNTRY = "Poland", REFERENCE = "2"
[3] COUNTRY = "United States of America", REFERENCE = "3"
[4] COUNTRY = "France", REFERENCE = "700_000"
----
In contrast to the default syntax used in `@CsvSource`, `@CsvFileSource` uses a double
quote (`+++"+++`) as the quote character by default, but this can be changed via the
`quoteCharacter` attribute. See the `"United States of America"` value in the example
above. An empty, quoted value (`+++""+++`) results in an empty `String` unless the
`emptyValue` attribute is set; whereas, an entirely _empty_ value is interpreted as a
`null` reference. By specifying one or more `nullValues`, a custom value can be
interpreted as a `null` reference. An `ArgumentConversionException` is thrown if the
target type of a `null` reference is a primitive type.
NOTE: An _unquoted_ empty value will always be converted to a `null` reference regardless
of any custom values configured via the `nullValues` attribute.
Except within a quoted string, leading and trailing whitespace in a CSV column is trimmed
by default. This behavior can be changed by setting the
`ignoreLeadingAndTrailingWhitespace` attribute to `true`.
[[sources-ArgumentsSource]]
=== @ArgumentsSource
`@ArgumentsSource` can be used to specify a custom, reusable `ArgumentsProvider`. Note
that an implementation of `ArgumentsProvider` must be declared as either a top-level
class or as a `static` nested class.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsSource_example]
----
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsProvider_example]
----
If you wish to implement a custom `ArgumentsProvider` that also consumes an annotation
(like built-in providers such as `{ValueArgumentsProvider}` or `{CsvArgumentsProvider}`),
you have the possibility to extend the `{AnnotationBasedArgumentsProvider}` class.
Moreover, `ArgumentsProvider` implementations may declare constructor parameters in case
they need to be resolved by a registered `ParameterResolver` as demonstrated in the
following example.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsProviderWithConstructorInjection_example]
----
[[repeatable-sources]]
=== Multiple sources using repeatable annotations
Repeatable annotations provide a convenient way to specify multiple sources from
different providers.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=repeatable_annotations]
----
Following the above parameterized test, a test case will run for each argument:
----
[1] foo
[2] bar
----
The following annotations are repeatable:
* `@ValueSource`
* `@EnumSource`
* `@MethodSource`
* `@FieldSource`
* `@CsvSource`
* `@CsvFileSource`
* `@ArgumentsSource`
[[argument-count-validation]]
== Argument Count Validation
By default, when an arguments source provides more arguments than the test method needs,
those additional arguments are ignored and the test executes as usual.
This can lead to bugs where arguments are never passed to the parameterized class or
method.
To prevent this, you can set argument count validation to 'strict'.
Then, any additional arguments will cause an error instead.
To change this behavior for all tests, set the
`junit.jupiter.params.argumentCountValidation`
xref:running-tests/configuration-parameters.adoc[configuration parameter] to `strict`.
To change this behavior for a single parameterized class or test method,
use the `argumentCountValidation` attribute of the `@ParameterizedClass` or
`@ParameterizedTest` annotation:
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=argument_count_validation]
----
[[argument-conversion]]
== Argument Conversion
[[argument-conversion-widening]]
=== Widening Conversion
JUnit Jupiter supports
https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.2[Widening Primitive
Conversion] for arguments supplied to a `@ParameterizedClass` or `@ParameterizedTest`.
For example, a parameterized class or test method annotated with
`@ValueSource(ints = { 1, 2, 3 })` can be declared to accept not only an argument of type
`int` but also an argument of type `long`, `float`, or `double`.
[[argument-conversion-implicit]]
=== Implicit Conversion
To support use cases like `@CsvSource`, JUnit Jupiter provides a number of built-in
implicit type converters. The conversion process depends on the declared type of each
method parameter.
For example, if a `@ParameterizedClass` or `@ParameterizedTest` declares a parameter
of type `TimeUnit` and the actual type supplied by the declared source is a `String`, the
string will be automatically converted into the corresponding `TimeUnit` enum constant.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=implicit_conversion_example]
----
`String` instances are implicitly converted to the following target types.
NOTE: Decimal, hexadecimal, and octal `String` literals will be converted to their
integral types: `byte`, `short`, `int`, `long`, and their boxed counterparts.
[[argument-conversion-implicit-table]]
[cols="10,90"]
|===
| Target Type | Example
| `boolean`/`Boolean` | `"true"` -> `true` _(only accepts values 'true' or 'false', case-insensitive)_
| `byte`/`Byte` | `"15"`, `"0xF"`, or `"017"` -> `(byte) 15`
| `char`/`Character` | `"o"` -> `'o'`
| `short`/`Short` | `"15"`, `"0xF"`, or `"017"` -> `(short) 15`
| `int`/`Integer` | `"15"`, `"0xF"`, or `"017"` -> `15`
| `long`/`Long` | `"15"`, `"0xF"`, or `"017"` -> `15L`
| `float`/`Float` | `"1.0"` -> `1.0f`
| `double`/`Double` | `"1.0"` -> `1.0d`
| `Enum` subclass | `"SECONDS"` -> `TimeUnit.SECONDS`
| `java.io.File` | `"/path/to/file"` -> `new File("/path/to/file")`
| `java.lang.Class` | `"java.lang.Integer"` -> `java.lang.Integer.class` _(use `$` for nested classes: `"java.lang.Thread$State"`)_
| `java.lang.Class` | `"byte"` -> `byte.class` _(primitive types are supported)_
| `java.lang.Class` | `"char[]"` -> `char[].class` _(array types are supported)_
| `java.math.BigDecimal` | `"123.456e789"` -> `new BigDecimal("123.456e789")`
| `java.math.BigInteger` | `"1234567890123456789"` -> `new BigInteger("1234567890123456789")`
| `java.net.URI` | `"https://junit.org/"` -> `URI.create("https://junit.org/")`
| `java.net.URL` | `"https://junit.org/"` -> `URI.create("https://junit.org/").toURL()`
| `java.nio.charset.Charset` | `"UTF-8"` -> `Charset.forName("UTF-8")`
| `java.nio.file.Path` | `"/path/to/file"` -> `Path.of("/path/to/file")`
| `java.time.Duration` | `"PT3S"` -> `Duration.ofSeconds(3)`
| `java.time.Instant` | `"1970-01-01T00:00:00Z"` -> `Instant.ofEpochMilli(0)`
| `java.time.LocalDateTime` | `"2017-03-14T12:34:56.789"` -> `LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000)`
| `java.time.LocalDate` | `"2017-03-14"` -> `LocalDate.of(2017, 3, 14)`
| `java.time.LocalTime` | `"12:34:56.789"` -> `LocalTime.of(12, 34, 56, 789_000_000)`
| `java.time.MonthDay` | `"--03-14"` -> `MonthDay.of(3, 14)`
| `java.time.OffsetDateTime` | `"2017-03-14T12:34:56.789Z"` -> `OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)`
| `java.time.OffsetTime` | `"12:34:56.789Z"` -> `OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC)`
| `java.time.Period` | `"P2M6D"` -> `Period.of(0, 2, 6)`
| `java.time.YearMonth` | `"2017-03"` -> `YearMonth.of(2017, 3)`
| `java.time.Year` | `"2017"` -> `Year.of(2017)`
| `java.time.ZonedDateTime` | `"2017-03-14T12:34:56.789Z"` -> `ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)`
| `java.time.ZoneId` | `"Europe/Berlin"` -> `ZoneId.of("Europe/Berlin")`
| `java.time.ZoneOffset` | `"+02:30"` -> `ZoneOffset.ofHoursMinutes(2, 30)`
| `java.util.Currency` | `"JPY"` -> `Currency.getInstance("JPY")`
| `java.util.Locale` | `"en-US"` -> `Locale.forLanguageTag("en-US")`
| `java.util.UUID` | `"d043e930-7b3b-48e3-bdbe-5a3ccfb833db"` -> `UUID.fromString("d043e930-7b3b-48e3-bdbe-5a3ccfb833db")`
|===
[[argument-conversion-implicit-fallback]]
==== Fallback String-to-Object Conversion
In addition to implicit conversion from strings to the target types listed in the above
table, JUnit Jupiter also provides a fallback mechanism for automatic conversion from a
`String` to a given target type if the target type declares a suitable _factory method_
or _factory constructor_ as defined below.
- __factory method__: a non-private, `static` method declared in the target type that
accepts either a single `String` argument or a single `CharSequence` argument and
returns an instance of the target type. The name of the method can be arbitrary and need
not follow any particular convention.
- __factory constructor__: a non-private constructor in the target type that accepts a
either a single `String` argument or a single `CharSequence` argument. Note that the
target type must be declared as either a top-level class or as a `static` nested class.
If there are multiple _factory methods_ or _factory constructors_, matching proceeds in the
following order:
1. A single _factory method_ accepting a `String` argument.
2. A single _factory constructor_ accepting a `String` argument.
3. A single _factory method_ accepting a `CharSequence` argument.
4. A single _factory constructor_ accepting a `CharSequence` argument.
5. A single _factory method_ accepting a `String` argument once all `@Deprecated` factory
methods have been removed from the set of methods being considered.
6. A single _factory method_ accepting a `CharSequence` argument once all `@Deprecated` factory
methods have been removed from the set of methods being considered.
For example, in the following `@ParameterizedTest` method, the `Book` argument will be
created by invoking the `Book.fromTitle(String)` factory method and passing `"42 Cats"`
as the title of the book.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=implicit_fallback_conversion_example]
----
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=implicit_fallback_conversion_example_Book]
----
[[argument-conversion-explicit]]
=== Explicit Conversion
Instead of relying on implicit argument conversion, you may explicitly specify an
`{ArgumentConverter}` to use for a certain parameter using the `@ConvertWith` annotation
like in the following example. Note that an implementation of `ArgumentConverter` must be
declared as either a top-level class or as a `static` nested class.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=explicit_conversion_example]
----
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=explicit_conversion_example_ToStringArgumentConverter]
----
If the converter is only meant to convert one type to another, you can extend
`TypedArgumentConverter` to avoid boilerplate type checks.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=explicit_conversion_example_TypedArgumentConverter]
----
Explicit argument converters are meant to be implemented by test and extension authors.
Thus, `junit-jupiter-params` only provides a single explicit argument converter that may
also serve as a reference implementation: `JavaTimeArgumentConverter`. It is used via the
composed annotation `JavaTimeConversionPattern`.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=explicit_java_time_converter]
----
If you wish to implement a custom `{ArgumentConverter}` that also consumes an annotation
(like `JavaTimeArgumentConverter`), you have the possibility to extend the
`{AnnotationBasedArgumentConverter}` class.
[[argument-aggregation]]
== Argument Aggregation
By default, each _argument_ provided to a `@ParameterizedClass` or `@ParameterizedTest`
corresponds to a single method parameter. Consequently, argument sources which are
expected to supply a large number of arguments can lead to large constructor or method
signatures, respectively.
In such cases, an `{ArgumentsAccessor}` can be used instead of multiple parameters. Using
this API, you can access the provided arguments through a single argument passed to your
test method. In addition, type conversion is supported as discussed in
<>.
Besides, you can retrieve the current test invocation index with
`ArgumentsAccessor.getInvocationIndex()`.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAccessor_example]
----
_An instance of `ArgumentsAccessor` is automatically injected into any parameter of type
`ArgumentsAccessor`._
[[argument-aggregation-custom]]
=== Custom Aggregators
Apart from direct access to the arguments of a `@ParameterizedClass` or
`@ParameterizedTest` using an `ArgumentsAccessor`, JUnit Jupiter also supports the usage
of custom, reusable _aggregators_.
To use a custom aggregator, implement the `{ArgumentsAggregator}` interface and register
it via the `@AggregateWith` annotation on a compatible parameter of the
`@ParameterizedClass` or `@ParameterizedTest`. The result of the aggregation will then be
provided as an argument for the corresponding parameter when the parameterized test is
invoked. Note that an implementation of `ArgumentsAggregator` must be declared as either a
top-level class or as a `static` nested class.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_example]
----
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_example_PersonAggregator]
----
If you find yourself repeatedly declaring `@AggregateWith(MyTypeAggregator.class)` for
multiple parameterized classes or methods across your codebase, you may wish to create a
custom _composed annotation_ such as `@CsvToMyType` that is meta-annotated with
`@AggregateWith(MyTypeAggregator.class)`. The following example demonstrates this in
action with a custom `@CsvToPerson` annotation.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_with_custom_annotation_example]
----
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_with_custom_annotation_example_CsvToPerson]
----
[[display-names]]
== Customizing Display Names
By default, the display name of a parameterized class or test invocation contains the
invocation index and a comma-separated list of the `String` representations of all
arguments for that specific invocation. If parameter names are present in the bytecode,
each argument will be preceded by its parameter name and an equals sign (unless the
argument is only available via an `ArgumentsAccessor` or `ArgumentAggregator`) – for
example, `firstName = "Jane"`.
[TIP]
====
To ensure that parameter names are present in the bytecode, test code must be compiled
with the `-parameters` compiler flag for Java or with the `-java-parameters` compiler flag
for Kotlin.
====
However, you can customize invocation display names via the `name` attribute of the
`@ParameterizedClass` or `@ParameterizedTest` annotation as in the following example.
======
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=custom_display_names]
----
When executing the above method using the `ConsoleLauncher` you will see output similar to
the following.
....
Display name of container ✔
├─ 1 ==> the rank of "apple" is "1" ✔
├─ 2 ==> the rank of "banana" is "2" ✔
└─ 3 ==> the rank of "lemon, lime" is "3" ✔
....
======
[NOTE]
====
Please note that `name` is a `MessageFormat` pattern. Thus, a single quote (`'`) needs to
be represented as a doubled single quote (`''`) in order to be displayed.
====
The following placeholders are supported within custom display names.
[cols="20,80"]
|===
| Placeholder | Description
| `\{displayName}` | the display name of the method
| `\{index}` | the current invocation index (1-based)
| `\{arguments}` | the complete, comma-separated arguments list
| `\{argumentsWithNames}` | the complete, comma-separated arguments list with parameter names
| `\{argumentSetName}` | the name of the argument set
| `\{argumentSetNameOrArgumentsWithNames}` | `\{argumentSetName}` or `\{argumentsWithNames}`, depending on how the arguments are supplied
| `\{0}`, `\{1}`, ... | an individual argument
|===
NOTE: When including arguments in display names, their string representations are truncated
if they exceed the configured maximum length. The limit is configurable via the
`junit.jupiter.params.displayname.argument.maxlength` configuration parameter and defaults
to 512 characters.
When using `@MethodSource`, `@FieldSource`, or `@ArgumentsSource`, you can provide custom
names for individual arguments or custom names for entire sets of arguments.
Use the `{Named}` API to provide a custom name for an individual argument, and the custom
name will be used if the argument is included in the invocation display name, like in the
example below.
======
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=named_arguments]
----
When executing the above method using the `ConsoleLauncher` you will see output similar to
the following.
....
A parameterized test with named arguments ✔
├─ 1: An important file ✔
└─ 2: Another file ✔
....
======
[NOTE]
====
Note that `arguments(Object...)` is a static factory method defined in the
`org.junit.jupiter.params.provider.Arguments` interface.
Similarly, `named(String, Object)` is a static factory method defined in the
`org.junit.jupiter.api.Named` interface.
====
Use the `ArgumentSet` API to provide a custom name for the entire set of arguments, and
the custom name will be used as the display name, like in the example below.
======
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=named_argument_set]
----
When executing the above method using the `ConsoleLauncher` you will see output similar to
the following.
....
A parameterized test with named argument sets ✔
├─ [1] Important files ✔
└─ [2] Other files ✔
....
======
[NOTE]
====
Note that `argumentSet(String, Object...)` is a static factory method defined in the
`org.junit.jupiter.params.provider.Arguments` interface.
====
[[display-names-quoted-text]]
=== Quoted Text-based Arguments
As of JUnit Jupiter 6.0, text-based arguments in display names for parameterized tests are
quoted by default. In this context, any `CharSequence` (such as a `String`) or `Character`
is considered text. A `CharSequence` is wrapped in double quotes (`"`), and a `Character`
is wrapped in single quotes (`'`).
Special characters will be escaped in the quoted text. For example, carriage returns and
line feeds will be escaped as `\\r` and `\\n`, respectively.
[TIP]
====
This feature can be disabled by setting the `quoteTextArguments` attributes in
`@ParameterizedClass` and `@ParameterizedTest` to `false`.
====
For example, given a string argument `"line 1\nline 2"`, the physical representation in
the display name will be `"\"line 1\\nline 2\""` which is printed as `"line 1\nline 2"`.
Similarly, given a string argument `"\t"`, the physical representation in the display name
will be `"\"\\t\""` which is printed as `"\t"` instead of a blank string or invisible tab
character. The same applies for a character argument `'\t'`, whose physical representation
in the display name would be `"'\\t'"` which is printed as `'\t'`.
For a concrete example, if you run the first `nullValuesAndBlankStrings(String text)`
parameterized test method from the <> section above, the following
display names are generated.
----
[1] text = null
[2] text = ""
[3] text = " "
[4] text = " "
[5] text = "\t"
[6] text = "\n"
----
If you run the first `testWithCsvSource(String fruit, int rank)` parameterized test method
from the <> section above, the following display names are generated.
----
[1] fruit = "apple", rank = "1"
[2] fruit = "banana", rank = "2"
[3] fruit = "lemon, lime", rank = "0xF1"
[4] fruit = "strawberry", rank = "700_000"
----
[NOTE]
====
The original source arguments are quoted when generating a display name, and this occurs
before any implicit or explicit argument conversion is performed.
For example, if a parameterized test accepts `3.14` as a `float` argument that was
converted from `"3.14"` as an input string, `"3.14"` will be present in the display name
instead of `3.14`. You can see the effect of this with the `rank` values in the above
example.
====
[[display-names-default-pattern]]
=== Default Display Name Pattern
If you'd like to set a default name pattern for all parameterized classes and tests in
your project, you can declare the `junit.jupiter.params.displayname.default` configuration
parameter in the `junit-platform.properties` file as demonstrated in the following example (see
xref:running-tests/configuration-parameters.adoc[] for other options).
[source,properties,indent=0]
----
junit.jupiter.params.displayname.default = {index}
----
[[display-names-precedence-rules]]
=== Precedence Rules
The display name for a parameterized class or test is determined according to the
following precedence rules:
1. `name` attribute in `@ParameterizedClass` or `@ParameterizedTest`, if present
2. value of the `junit.jupiter.params.displayname.default` configuration parameter, if present
3. `DEFAULT_DISPLAY_NAME` constant defined in
`org.junit.jupiter.params.ParameterizedInvocationConstants`
[[lifecycle-interop]]
== Lifecycle and Interoperability
[[lifecycle-interop-methods]]
=== Parameterized Tests
Each invocation of a parameterized test has the same lifecycle as a regular `@Test`
method. For example, `@BeforeEach` methods will be executed before each invocation.
Similar to xref:writing-tests/dynamic-tests.adoc[], invocations will appear one by one in the
test tree of an IDE. You may at will mix regular `@Test` methods and `@ParameterizedTest`
methods within the same test class.
You may use `ParameterResolver` extensions with `@ParameterizedTest` methods. However,
method parameters that are resolved by argument sources need to come first in the
parameter list. Since a test class may contain regular tests as well as parameterized
tests with different parameter lists, values from argument sources are not resolved for
lifecycle methods (for example, `@BeforeEach`) and test class constructors.
[source,java,indent=0]
----
include::example$java/example/ParameterizedTestDemo.java[tags=ParameterResolver_example]
----
[[lifecycle-interop-classes]]
=== Parameterized Classes
Each invocation of a parameterized class has the same lifecycle as a regular test class.
For example, `@BeforeAll` methods will be executed _once_ before all invocations and
`@BeforeEach` methods will be executed before each _test method_ invocation. Similar to
xref:writing-tests/dynamic-tests.adoc[], invocations will appear one by one in the test tree of an
IDE.
You may use `ParameterResolver` extensions with `@ParameterizedClass` constructors.
However, if constructor injection is used, constructor parameters that are resolved by
argument sources need to come first in the parameter list. Values from argument sources
are not resolved for regular lifecycle methods (for example, `@BeforeEach`).
In addition to regular lifecycle methods, parameterized classes may declare
`{BeforeParameterizedClassInvocation}` and `{AfterParameterizedClassInvocation}` lifecycle
methods that are called once before/after each invocation of the parameterized class.
These methods must be `static` unless the parameterized class is configured to use
`@TestInstance(Lifecycle.PER_CLASS)` (see xref:writing-tests/test-instance-lifecycle.adoc[]).
These lifecycle methods may optionally declare parameters that are resolved depending on
the setting of the `injectArguments` annotation attribute. If it is set to `false`, the
parameters must be resolved by other registered {ParameterResolver} extensions. If the
attribute is set to `true` (the default), the method may declare parameters that match the
arguments of the parameterized class (see the Javadoc of
`{BeforeParameterizedClassInvocation}` and `{AfterParameterizedClassInvocation}` for
details). This may, for example, be used to initialize the used arguments as demonstrated
by the following example.
[source,java,indent=0]
.Using parameterized class lifecycle methods
----
include::example$java/example/ParameterizedLifecycleDemo.java[tags=example]
----
<1> Initialization of the argument _before_ each invocation of the parameterized class
<2> Usage of the previously initialized argument in a test method
<3> Validation and cleanup of the argument _after_ each invocation of the parameterized
class
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/repeated-tests.adoc
================================================
= Repeated Tests
JUnit Jupiter provides the ability to repeat a test a specified number of times by
annotating a method with `@RepeatedTest` and specifying the total number of repetitions
desired. Each invocation of a repeated test behaves like the execution of a regular
`@Test` method with full support for the same lifecycle callbacks and extensions.
The following example demonstrates how to declare a test named `repeatedTest()` that
will be automatically repeated 10 times.
[tabs]
====
Java::
+
--
[source,java]
----
@RepeatedTest(10)
void repeatedTest() {
// ...
}
----
--
Kotlin::
+
--
[source,kotlin]
----
@RepeatedTest(10)
fun repeatedTest() {
// ...
}
----
--
====
`@RepeatedTest` can be configured with a failure threshold which signifies the number of
failures after which remaining repetitions will be automatically skipped. Set the
`failureThreshold` attribute to a positive number less than the total number of
repetitions in order to skip the invocations of remaining repetitions after the specified
number of failures has been encountered.
For example, if you are using `@RepeatedTest` to repeatedly invoke a test that you suspect
to be _flaky_, a single failure is sufficient to demonstrate that the test is flaky, and
there is no need to invoke the remaining repetitions. To support that specific use case,
set `failureThreshold = 1`. You can alternatively set the threshold to a number greater
than 1 depending on your use case.
By default, the `failureThreshold` attribute is set to `Integer.MAX_VALUE`, signaling that
no failure threshold will be applied, which effectively means that the specified number of
repetitions will be invoked regardless of whether any repetitions fail.
WARNING: If the repetitions of a `@RepeatedTest` method are executed in parallel, no
guarantees can be made regarding the failure threshold. It is therefore recommended that a
`@RepeatedTest` method be annotated with `@Execution(SAME_THREAD)` when parallel execution
is configured. See xref:writing-tests/parallel-execution.adoc[] for further details.
In addition to specifying the number of repetitions and failure threshold, a custom
display name can be configured for each repetition via the `name` attribute of the
`@RepeatedTest` annotation. Furthermore, the display name can be a pattern composed of a
combination of static text and dynamic placeholders. The following placeholders are
currently supported.
- `+{displayName}+`: display name of the `@RepeatedTest` method
- `+{currentRepetition}+`: the current repetition count
- `+{totalRepetitions}+`: the total number of repetitions
The default display name for a given repetition is generated based on the following
pattern: `"repetition +{currentRepetition}+ of +{totalRepetitions}+"`.Thus, the display
names for individual repetitions of the previous `repeatedTest()` example would be:
`repetition 1 of 10`, `repetition 2 of 10`, etc.If you would like the display name of
the `@RepeatedTest` method included in the name of each repetition, you can define your
own custom pattern or use the predefined `RepeatedTest.LONG_DISPLAY_NAME` pattern.The
latter is equal to `"+{displayName}+ :: repetition +{currentRepetition}+ of
+{totalRepetitions}+"` which results in display names for individual repetitions like
`repeatedTest() :: repetition 1 of 10`, `repeatedTest() :: repetition 2 of 10`, etc.
In order to retrieve information about the current repetition, the total number of
repetitions, the number of repetitions that have failed, and the failure threshold, a
developer can choose to have an instance of `{RepetitionInfo}` injected into a
`@RepeatedTest`, `@BeforeEach`, or `@AfterEach` method.
[[examples]]
== Repeated Test Examples
The `RepeatedTestsDemo` class at the end of this section demonstrates several examples of
repeated tests.
The `repeatedTest()` method is identical to the example from the previous section; whereas,
`repeatedTestWithRepetitionInfo()` demonstrates how to have an instance of
`RepetitionInfo` injected into a test to access the total number of repetitions for the
current repeated test.
`repeatedTestWithFailureThreshold()` demonstrates how to set a failure threshold and
simulates an unexpected failure for every second repetition.The resulting behavior can be
viewed in the `ConsoleLauncher` output at the end of this section.
The next two methods demonstrate how to include a custom `@DisplayName` for the
`@RepeatedTest` method in the display name of each repetition. `customDisplayName()`
combines a custom display name with a custom pattern and then uses `TestInfo` to verify
the format of the generated display name. `Repeat!` is the `+{displayName}+` which comes
from the `@DisplayName` declaration, and `1/1` comes from
`+{currentRepetition}+/+{totalRepetitions}+`.In contrast,
`customDisplayNameWithLongPattern()` uses the aforementioned predefined
`RepeatedTest.LONG_DISPLAY_NAME` pattern.
`repeatedTestInGerman()` demonstrates the ability to translate display names of repeated
tests into foreign languages -- in this case German, resulting in names for individual
repetitions such as: `Wiederholung 1 von 5`, `Wiederholung 2 von 5`, etc.
Since the `beforeEach()` method is annotated with `@BeforeEach` it will get executed
before each repetition of each repeated test. By having the `TestInfo` and
`RepetitionInfo` injected into the method, we see that it's possible to obtain
information about the currently executing repeated test. Executing `RepeatedTestsDemo`
with the `INFO` log level enabled results in the following output.
....
INFO: About to execute repetition 1 of 10 for repeatedTest
INFO: About to execute repetition 2 of 10 for repeatedTest
INFO: About to execute repetition 3 of 10 for repeatedTest
INFO: About to execute repetition 4 of 10 for repeatedTest
INFO: About to execute repetition 5 of 10 for repeatedTest
INFO: About to execute repetition 6 of 10 for repeatedTest
INFO: About to execute repetition 7 of 10 for repeatedTest
INFO: About to execute repetition 8 of 10 for repeatedTest
INFO: About to execute repetition 9 of 10 for repeatedTest
INFO: About to execute repetition 10 of 10 for repeatedTest
INFO: About to execute repetition 1 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 2 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 3 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 4 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 5 of 5 for repeatedTestWithRepetitionInfo
INFO: About to execute repetition 1 of 8 for repeatedTestWithFailureThreshold
INFO: About to execute repetition 2 of 8 for repeatedTestWithFailureThreshold
INFO: About to execute repetition 3 of 8 for repeatedTestWithFailureThreshold
INFO: About to execute repetition 4 of 8 for repeatedTestWithFailureThreshold
INFO: About to execute repetition 1 of 1 for customDisplayName
INFO: About to execute repetition 1 of 1 for customDisplayNameWithLongPattern
INFO: About to execute repetition 1 of 5 for repeatedTestInGerman
INFO: About to execute repetition 2 of 5 for repeatedTestInGerman
INFO: About to execute repetition 3 of 5 for repeatedTestInGerman
INFO: About to execute repetition 4 of 5 for repeatedTestInGerman
INFO: About to execute repetition 5 of 5 for repeatedTestInGerman
....
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/RepeatedTestsDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/RepeatedTestsDemo.kt[tags=user_guide]
----
--
====
When using the `ConsoleLauncher` with the unicode theme enabled, execution of
`RepeatedTestsDemo` results in the following output to the console.
....
├─ RepeatedTestsDemo ✔
│ ├─ repeatedTest() ✔
│ │ ├─ repetition 1 of 10 ✔
│ │ ├─ repetition 2 of 10 ✔
│ │ ├─ repetition 3 of 10 ✔
│ │ ├─ repetition 4 of 10 ✔
│ │ ├─ repetition 5 of 10 ✔
│ │ ├─ repetition 6 of 10 ✔
│ │ ├─ repetition 7 of 10 ✔
│ │ ├─ repetition 8 of 10 ✔
│ │ ├─ repetition 9 of 10 ✔
│ │ └─ repetition 10 of 10 ✔
│ ├─ repeatedTestWithRepetitionInfo(RepetitionInfo) ✔
│ │ ├─ repetition 1 of 5 ✔
│ │ ├─ repetition 2 of 5 ✔
│ │ ├─ repetition 3 of 5 ✔
│ │ ├─ repetition 4 of 5 ✔
│ │ └─ repetition 5 of 5 ✔
│ ├─ repeatedTestWithFailureThreshold(RepetitionInfo) ✔
│ │ ├─ repetition 1 of 8 ✔
│ │ ├─ repetition 2 of 8 ✘ Boom!
│ │ ├─ repetition 3 of 8 ✔
│ │ ├─ repetition 4 of 8 ✘ Boom!
│ │ ├─ repetition 5 of 8 ↷ Failure threshold [2] exceeded
│ │ ├─ repetition 6 of 8 ↷ Failure threshold [2] exceeded
│ │ ├─ repetition 7 of 8 ↷ Failure threshold [2] exceeded
│ │ └─ repetition 8 of 8 ↷ Failure threshold [2] exceeded
│ ├─ Repeat! ✔
│ │ └─ Repeat! 1/1 ✔
│ ├─ Details... ✔
│ │ └─ Details... :: repetition 1 of 1 ✔
│ └─ repeatedTestInGerman() ✔
│ ├─ Wiederholung 1 von 5 ✔
│ ├─ Wiederholung 2 von 5 ✔
│ ├─ Wiederholung 3 von 5 ✔
│ ├─ Wiederholung 4 von 5 ✔
│ └─ Wiederholung 5 von 5 ✔
....
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/tagging-and-filtering.adoc
================================================
= Tagging and Filtering
Test classes and methods can be tagged via the `@Tag` annotation. Those tags can later be
used to filter xref:running-tests/intro.adoc[test discovery and execution]. Please refer to the
xref:running-tests/tags.adoc[] section for more information about tag support in the JUnit
Platform.
[source,java,indent=0]
----
include::example$java/example/TaggingDemo.java[tags=user_guide]
----
TIP: See xref:writing-tests/annotations.adoc#annotations[Meta-Annotations and Composed Annotations] for examples demonstrating how to create
custom annotations for tags.
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/test-classes-and-methods.adoc
================================================
= Test Classes and Methods
Test methods and lifecycle methods may be declared locally within the current test class,
inherited from superclasses, or inherited from interfaces (see
xref:writing-tests/test-interfaces-and-default-methods.adoc[]). In addition, test methods and
lifecycle methods must not be `abstract` and must not return a value (except `@TestFactory`
methods which are required to return a value).
[NOTE]
.Class and method visibility
====
Test classes, test methods, and lifecycle methods are not required to be `public`, but
they must _not_ be `private`.
It is generally recommended to omit the `public` modifier for test classes, test methods,
and lifecycle methods unless there is a technical reason for doing so – for example, when
a test class is extended by a test class in another package. Another technical reason for
making classes and methods `public` is to simplify testing on the module path when using
the Java Module System.
====
[NOTE]
.Field and method inheritance
====
Fields in test classes are inherited. For example, a `@TempDir` field from a superclass
will always be applied in a subclass.
Test methods and lifecycle methods are inherited unless they are overridden according to
the visibility rules of the Java language. For example, a `@Test` method from a superclass
will always be applied in a subclass unless the subclass explicitly overrides the method.
Similarly, if a package-private `@Test` method is declared in a superclass that resides in
a different package than the subclass, that `@Test` method will always be applied in the
subclass since the subclass cannot override a package-private method from a superclass in
a different package.
See also: xref:extensions/supported-utilities-in-extensions.adoc#search-semantics[Field and Method Search Semantics]
====
The following test class demonstrates the use of `@Test` methods and all supported
lifecycle methods. For further information on runtime semantics, see
xref:writing-tests/test-execution-order.adoc[] and
xref:extensions/relative-execution-order-of-user-code-and-extensions.adoc#wrapping-behavior[Wrapping Behavior of Callbacks].
.A standard test class
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/StandardTests.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/StandardTests.kt[tags=user_guide]
----
--
====
It is also possible to use Java `record` classes as test classes as illustrated by the
following example.
[source,java,indent=0]
.A test class written as a Java record
----
include::example$java/example/MyFirstJUnitJupiterRecordTests.java[tags=user_guide]
----
[[kotlin-coroutines]]
== Kotlin Coroutines
Test methods and lifecycle methods may be written in Kotlin and may optionally use the `suspend`
keyword for testing code using coroutines.
[source,kotlin]
.A test class written in Kotlin
----
include::example$kotlin/example/KotlinCoroutinesDemo.kt[tags=user_guide]
----
NOTE: Using suspending functions as test or lifecycle methods requires
https://central.sonatype.com/artifact/org.jetbrains.kotlin/kotlin-stdlib[`kotlin-stdlib`],
https://central.sonatype.com/artifact/org.jetbrains.kotlin/kotlin-reflect[`kotlin-reflect`],
and
https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core[`kotlinx-coroutines-core`]
to be present on the classpath or module path.
When using `suspend` test or lifecycle methods, JUnit internally uses `runBlocking` to execute them.
This is sufficient for simple coroutine-based tests.
However, `runBlocking` does not support skipping calls to `delay` and does not provide control over
virtual time or dispatchers.
As an alternative, tests can be written as regular `@Test` methods and wrapped in `runTest` from
https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-test[`kotlinx-coroutines-test`]
to support skipping delays during tests and gain control over virtual time and dispatchers.
[source,kotlin]
----
include::example$kotlin/example/KotlinCoroutinesRunTestDemo.kt[tags=user_guide]
----
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/test-execution-order.adoc
================================================
= Test Execution Order
By default, test classes and methods will be ordered using an algorithm that is
deterministic but intentionally nonobvious. This ensures that subsequent runs of a test
suite execute test classes and test methods in the same order, thereby allowing for
repeatable builds.
NOTE: See xref:writing-tests/definitions.adoc[] for a definition of _test method_ and _test class_.
[[methods]]
== Method Order
Although true _unit tests_ typically should not rely on the order in which they are
executed, there are times when it is necessary to enforce a specific test method execution
order -- for example, when writing _integration tests_ or _functional tests_ where the
sequence of the tests is important, especially in conjunction with
`@TestInstance(Lifecycle.PER_CLASS)`.
To control the order in which test methods are executed, annotate your test class or test
interface with `{TestMethodOrder}` and specify the desired `{MethodOrderer}`
implementation. You can implement your own custom `MethodOrderer` or use one of the
following built-in `MethodOrderer` implementations.
* `{MethodOrderer_DisplayName}`: sorts test methods _alphanumerically_ based on their
display names (see xref:writing-tests/display-names.adoc#generator-precedence-rules[display name
generation precedence rules])
* `{MethodOrderer_MethodName}`: sorts test methods _alphanumerically_ based on their names
and formal parameter lists
* `{MethodOrderer_OrderAnnotation}`: sorts test methods _numerically_ based on values
specified via the `{Order}` annotation
* `{MethodOrderer_Random}`: orders test methods _pseudo-randomly_ and supports
configuration of a custom _seed_
The `MethodOrderer` configured on a test class is inherited by the `@Nested` test classes
it contains, recursively. If you want to avoid that a `@Nested` test class uses the same
`MethodOrderer` as its enclosing class, you can specify `{MethodOrderer_Default}` together
with `{TestMethodOrder}`.
NOTE: See also: xref:extensions/relative-execution-order-of-user-code-and-extensions.adoc#wrapping-behavior[Wrapping Behavior of Callbacks]
The following example demonstrates how to guarantee that test methods are executed in the
order specified via the `@Order` annotation.
[source,java,indent=0]
----
include::example$java/example/OrderedTestsDemo.java[tags=user_guide]
----
[[methods-default]]
=== Setting the Default Method Orderer
You can use the `junit.jupiter.testmethod.order.default` xref:running-tests/configuration-parameters.adoc[configuration parameter] to specify the fully qualified class name of the
`{MethodOrderer}` you would like to use by default. Just like for the orderer configured
via the `{TestMethodOrder}` annotation, the supplied class has to implement the
`MethodOrderer` interface. The default orderer will be used for all tests unless the
`@TestMethodOrder` annotation is present on an enclosing test class or test interface.
For example, to use the `{MethodOrderer_OrderAnnotation}` method orderer by default, you
should set the configuration parameter to the corresponding fully qualified class name
(e.g., in `src/test/resources/junit-platform.properties`):
[source,properties,indent=0]
----
junit.jupiter.testmethod.order.default = \
org.junit.jupiter.api.MethodOrderer$OrderAnnotation
----
Similarly, you can specify the fully qualified name of any custom class that implements
`MethodOrderer`.
[[classes]]
== Class Order
Although test classes typically should not rely on the order in which they are executed,
there are times when it is desirable to enforce a specific test class execution order. You
may wish to execute test classes in a random order to ensure there are no accidental
dependencies between test classes, or you may wish to order test classes to optimize build
time as outlined in the following scenarios.
* Run previously failing tests and faster tests first: "fail fast" mode
* With parallel execution enabled, schedule longer tests first: "shortest test plan
execution duration" mode
* Various other use cases
To configure test class execution order _globally_ for the entire test suite, use the
`junit.jupiter.testclass.order.default` xref:running-tests/configuration-parameters.adoc[configuration
parameter] to specify the fully qualified class name of the `{ClassOrderer}` you would
like to use. The supplied class must implement the `ClassOrderer` interface.
You can implement your own custom `ClassOrderer` or use one of the following built-in
`ClassOrderer` implementations.
* `{ClassOrderer_ClassName}`: sorts test classes _alphanumerically_ based on their fully
qualified class names
* `{ClassOrderer_DisplayName}`: sorts test classes _alphanumerically_ based on their
display names (see xref:writing-tests/display-names.adoc#generator-precedence-rules[display name
generation precedence rules])
* `{ClassOrderer_OrderAnnotation}`: sorts test classes _numerically_ based on values
specified via the `{Order}` annotation
* `{ClassOrderer_Random}`: orders test classes _pseudo-randomly_ and supports
configuration of a custom _seed_
For example, for the `@Order` annotation to be honored on _test classes_, you should
configure the `{ClassOrderer_OrderAnnotation}` class orderer using the configuration
parameter with the corresponding fully qualified class name (e.g., in
`src/test/resources/junit-platform.properties`):
[source,properties,indent=0]
----
junit.jupiter.testclass.order.default = \
org.junit.jupiter.api.ClassOrderer$OrderAnnotation
----
The configured `ClassOrderer` will be applied to all top-level test classes (including
`static` nested test classes) and `@Nested` test classes.
NOTE: Top-level test classes will be ordered relative to each other; whereas, `@Nested`
test classes will be ordered relative to other `@Nested` test classes sharing the same
_enclosing class_.
To configure test class execution order _locally_ for `@Nested` test classes, declare the
`{TestClassOrder}` annotation on the enclosing class for the `@Nested` test classes you
want to order, and supply a class reference to the `ClassOrderer` implementation you would
like to use directly in the `@TestClassOrder` annotation. The configured `ClassOrderer`
will be applied recursively to `@Nested` test classes and their `@Nested` test classes.
If you want to avoid that a `@Nested` test class uses the same `ClassOrderer` as its
enclosing class, you can specify `{ClassOrderer_Default}` together with `@TestClassOrder`.
Note that a local `@TestClassOrder` declaration always overrides an inherited
`@TestClassOrder` declaration or a `ClassOrderer` configured globally via the
`junit.jupiter.testclass.order.default` configuration parameter.
The following example demonstrates how to guarantee that `@Nested` test classes are
executed in the order specified via the `@Order` annotation.
[source,java,indent=0]
----
include::example$java/example/OrderedNestedTestClassesDemo.java[tags=user_guide]
----
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/test-instance-lifecycle.adoc
================================================
= Test Instance Lifecycle
In order to allow individual test methods to be executed in isolation and to avoid
unexpected side effects due to mutable test instance state, JUnit creates a new instance
of each test class before executing each _test method_ (see
xref:writing-tests/definitions.adoc[]). This "per-method" test instance lifecycle is the default
behavior in JUnit Jupiter and is analogous to all previous versions of JUnit.
NOTE: Please note that the test class will still be instantiated if a given _test method_
is _disabled_ via a xref:writing-tests/conditional-test-execution.adoc[condition] (e.g., `@Disabled`,
`@DisabledOnOs`, etc.) even when the "per-method" test instance lifecycle mode is active.
If you would prefer that JUnit Jupiter execute all test methods on the same test
instance, annotate your test class with `@TestInstance(Lifecycle.PER_CLASS)`. When using
this mode, a new test instance will be created once per test class. Thus, if your test
methods rely on state stored in instance variables, you may need to reset that state in
`@BeforeEach` or `@AfterEach` methods.
The "per-class" mode has some additional benefits over the default "per-method" mode.
Specifically, with the "per-class" mode it becomes possible to declare `@BeforeAll` and
`@AfterAll` on non-static methods as well as on interface `default` methods.
If you are authoring tests using the Kotlin programming language, you may also find it
easier to implement non-static `@BeforeAll` and `@AfterAll` lifecycle methods as well as
`@MethodSource` factory methods by switching to the "per-class" test instance lifecycle
mode.
[[default]]
== Changing the Default Test Instance Lifecycle
If a test class or test interface is not annotated with `@TestInstance`, JUnit Jupiter
will use a _default_ lifecycle mode. The standard _default_ mode is `PER_METHOD`;
however, it is possible to change the _default_ for the execution of an entire test plan.
To change the default test instance lifecycle mode, set the
`junit.jupiter.testinstance.lifecycle.default` _configuration parameter_ to the name of
an enum constant defined in `TestInstance.Lifecycle`, ignoring case. This can be supplied
as a JVM system property, as a _configuration parameter_ in the
`LauncherDiscoveryRequest` that is passed to the `Launcher`, or via the JUnit Platform
configuration file (see xref:running-tests/configuration-parameters.adoc[] for details).
For example, to set the default test instance lifecycle mode to `Lifecycle.PER_CLASS`,
you can start your JVM with the following system property.
`-Djunit.jupiter.testinstance.lifecycle.default=per_class`
Note, however, that setting the default test instance lifecycle mode via the JUnit
Platform configuration file is a more robust solution since the configuration file can be
checked into a version control system along with your project and can therefore be used
within IDEs and your build software.
To set the default test instance lifecycle mode to `Lifecycle.PER_CLASS` via the JUnit
Platform configuration file, create a file named `junit-platform.properties` in the root
of the class path (e.g., `src/test/resources`) with the following content.
`junit.jupiter.testinstance.lifecycle.default = per_class`
WARNING: Changing the _default_ test instance lifecycle mode can lead to unpredictable
results and fragile builds if not applied consistently. For example, if the build
configures "per-class" semantics as the default but tests in the IDE are executed using
"per-method" semantics, that can make it difficult to debug errors that occur on the
build server. It is therefore recommended to change the default in the JUnit Platform
configuration file instead of via a JVM system property.
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/test-interfaces-and-default-methods.adoc
================================================
= Test Interfaces and Default Methods
JUnit Jupiter allows `@Test`, `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`,
`@TestTemplate`, `@BeforeEach`, and `@AfterEach` to be declared on interface `default`
methods. `@BeforeAll` and `@AfterAll` can either be declared on `static` methods in a
test interface or on interface `default` methods _if_ the test interface or test class is
annotated with `@TestInstance(Lifecycle.PER_CLASS)` (see
xref:writing-tests/test-instance-lifecycle.adoc[]). Here are some examples.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/testinterface/TestLifecycleLogger.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/testinterface/TestLifecycleLogger.kt[tags=user_guide]
----
--
====
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/testinterface/TestInterfaceDynamicTestsDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/testinterface/TestInterfaceDynamicTestsDemo.kt[tags=user_guide]
----
--
====
`@ExtendWith` and `@Tag` can be declared on a test interface so that classes that
implement the interface automatically inherit its tags and extensions. See
xref:extensions/test-lifecycle-callbacks.adoc#before-after-execution[Before and After Test Execution Callbacks] for the source code of the
xref:extensions/test-lifecycle-callbacks.adoc#timing-extension[TimingExtension].
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/testinterface/TimeExecutionLogger.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/testinterface/TimeExecutionLogger.kt[tags=user_guide]
----
--
====
In your test class you can then implement these test interfaces to have them applied.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/testinterface/TestInterfaceDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/testinterface/TestInterfaceDemo.kt[tags=user_guide]
----
--
====
Running the `TestInterfaceDemo` results in output similar to the following:
....
INFO example.TestLifecycleLogger - Before all tests
INFO example.TestLifecycleLogger - About to execute [dynamicTestsForPalindromes()]
INFO example.TimingExtension - Method [dynamicTestsForPalindromes] took 19 ms.
INFO example.TestLifecycleLogger - Finished executing [dynamicTestsForPalindromes()]
INFO example.TestLifecycleLogger - About to execute [isEqualValue()]
INFO example.TimingExtension - Method [isEqualValue] took 1 ms.
INFO example.TestLifecycleLogger - Finished executing [isEqualValue()]
INFO example.TestLifecycleLogger - After all tests
....
Another possible application of this feature is to write tests for interface contracts.
For example, you can write tests for how implementations of `Object.equals` or
`Comparable.compareTo` should behave as follows.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/defaultmethods/Testable.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/defaultmethods/Testable.kt[tags=user_guide]
----
--
====
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/defaultmethods/EqualsContract.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/defaultmethods/EqualsContract.kt[tags=user_guide]
----
--
====
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/defaultmethods/ComparableContract.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/defaultmethods/ComparableContract.kt[tags=user_guide]
----
--
====
In your test class you can then implement both contract interfaces thereby inheriting the
corresponding tests. Of course you'll have to implement the abstract methods.
[tabs]
====
Java::
+
--
[source,java,indent=0]
----
include::example$java/example/defaultmethods/StringTests.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin,indent=0]
----
include::example$kotlin/example/kotlin/defaultmethods/StringTests.kt[tags=user_guide]
----
--
====
NOTE: The above tests are merely meant as examples and therefore not complete.
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/test-templates.adoc
================================================
= Test Templates
A `{TestTemplate}` method is not a regular test case but rather a template for a test
case. As such, it is designed to be invoked multiple times depending on the number of
invocation contexts returned by the registered providers. Thus, it must be used in
conjunction with a registered `{TestTemplateInvocationContextProvider}` extension. Each
invocation of a test template method behaves like the execution of a regular `@Test`
method with full support for the same lifecycle callbacks and extensions. Please refer to
xref:extensions/providing-invocation-contexts-for-test-templates.adoc[] for usage examples.
NOTE: xref:writing-tests/repeated-tests.adoc[] and
xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Tests] are built-in specializations of
test templates.
================================================
FILE: documentation/modules/ROOT/pages/writing-tests/timeouts.adoc
================================================
= Timeouts
The `@Timeout` annotation allows one to declare that a test, test factory, test template,
or lifecycle method should fail if its execution time exceeds a given duration. The time
unit for the duration defaults to seconds but is configurable.
The following example shows how `@Timeout` is applied to lifecycle and test methods.
[tabs]
====
Java::
+
--
[source,java]
----
include::example$java/example/TimeoutDemo.java[tags=user_guide]
----
--
Kotlin::
+
--
[source,kotlin]
----
include::example$kotlin/example/kotlin/TimeoutDemo.kt[tags=user_guide]
----
--
====
To apply the same timeout to all test methods within a test class and all of its `@Nested`
classes, you can declare the `@Timeout` annotation at the class level. It will then be
applied to all test, test factory, and test template methods within that class and its
`@Nested` classes unless overridden by a `@Timeout` annotation on a specific method or
`@Nested` class. Please note that `@Timeout` annotations declared at the class level are
not applied to lifecycle methods.
Declaring `@Timeout` on a `@TestFactory` method checks that the factory method returns
within the specified duration but does not verify the execution time of each individual
`DynamicTest` generated by the factory. Please use
`assertTimeout()` or `assertTimeoutPreemptively()` for that purpose.
If `@Timeout` is present on a `@TestTemplate` method — for example, a `@RepeatedTest` or
`@ParameterizedTest` — each invocation will have the given timeout applied to it.
[[thread-mode]]
== Thread mode
The timeout can be applied using one of the following three thread modes: `SAME_THREAD`,
`SEPARATE_THREAD`, or `INFERRED`.
When `SAME_THREAD` is used, the execution of the annotated method proceeds in the main
thread of the test. If the timeout is exceeded, the main thread is interrupted from
another thread. This is done to ensure interoperability with frameworks such as Spring
that make use of mechanisms that are sensitive to the currently running thread — for
example, `ThreadLocal` transaction management.
On the contrary when `SEPARATE_THREAD` is used, like the `assertTimeoutPreemptively()`
assertion, the execution of the annotated method proceeds in a separate thread, this
can lead to undesirable side effects, see xref:writing-tests/assertions.adoc#preemptive-timeouts[Preemptive Timeouts with `assertTimeoutPreemptively()`].
When `INFERRED` (default) thread mode is used, the thread mode is resolved via the
`junit.jupiter.execution.timeout.thread.mode.default` configuration parameter. If the
provided configuration parameter is invalid or not present then `SAME_THREAD` is used as
fallback.
[[default-timeouts]]
== Default Timeouts
The following xref:running-tests/configuration-parameters.adoc[configuration parameters] can be used to
specify default timeouts for all methods of a certain category unless they or an enclosing
test class is annotated with `@Timeout`:
`junit.jupiter.execution.timeout.default`::
Default timeout for all testable and lifecycle methods
`junit.jupiter.execution.timeout.testable.method.default`::
Default timeout for all testable methods
`junit.jupiter.execution.timeout.test.method.default`::
Default timeout for `@Test` methods
`junit.jupiter.execution.timeout.testtemplate.method.default`::
Default timeout for `@TestTemplate` methods
`junit.jupiter.execution.timeout.testfactory.method.default`::
Default timeout for `@TestFactory` methods
`junit.jupiter.execution.timeout.lifecycle.method.default`::
Default timeout for all lifecycle methods
`junit.jupiter.execution.timeout.beforeall.method.default`::
Default timeout for `@BeforeAll` methods
`junit.jupiter.execution.timeout.beforeeach.method.default`::
Default timeout for `@BeforeEach` methods
`junit.jupiter.execution.timeout.aftereach.method.default`::
Default timeout for `@AfterEach` methods
`junit.jupiter.execution.timeout.afterall.method.default`::
Default timeout for `@AfterAll` methods
More specific configuration parameters override less specific ones. For example,
`junit.jupiter.execution.timeout.test.method.default` overrides
`junit.jupiter.execution.timeout.testable.method.default` which overrides
`junit.jupiter.execution.timeout.default`.
The values of such configuration parameters must be in the following, case-insensitive
format: ` [ns|μs|ms|s|m|h|d]`. The space between the number and the unit may be
omitted. Specifying no unit is equivalent to using seconds.
.Example timeout configuration parameter values
[cols="20,80"]
|===
| Parameter value | Equivalent annotation
| `42` | `@Timeout(42)`
| `42 ns` | `@Timeout(value = 42, unit = NANOSECONDS)`
| `42 μs` | `@Timeout(value = 42, unit = MICROSECONDS)`
| `42 ms` | `@Timeout(value = 42, unit = MILLISECONDS)`
| `42 s` | `@Timeout(value = 42, unit = SECONDS)`
| `42 m` | `@Timeout(value = 42, unit = MINUTES)`
| `42 h` | `@Timeout(value = 42, unit = HOURS)`
| `42 d` | `@Timeout(value = 42, unit = DAYS)`
|===
[[polling]]
== Using @Timeout for Polling Tests
When dealing with asynchronous code, it is common to write tests that poll while waiting
for something to happen before performing any assertions. In some cases you can rewrite
the logic to use a `CountDownLatch` or another synchronization mechanism, but sometimes
that is not possible — for example, if the subject under test sends a message to a channel
in an external message broker and assertions cannot be performed until the message has
been successfully sent through the channel. Asynchronous tests like these require some
form of timeout to ensure they don't hang the test suite by executing indefinitely, as
would be the case if an asynchronous message never gets successfully delivered.
By configuring a timeout for an asynchronous test that polls, you can ensure that the test
does not execute indefinitely. The following example demonstrates how to achieve this with
JUnit Jupiter's `@Timeout` annotation. This technique can be used to implement "poll
until" logic very easily.
[tabs]
====
Java::
+
--
[source,java]
----
include::example$java/example/PollingTimeoutDemo.java[tags=user_guide,indent=0]
----
--
Kotlin::
+
--
[source,kotlin]
----
include::example$kotlin/example/kotlin/PollingTimeoutDemo.kt[tags=user_guide,indent=0]
----
--
====
NOTE: If you need more control over polling intervals and greater flexibility with
asynchronous tests, consider using a dedicated library such as
link:https://github.com/awaitility/awaitility[Awaitility].
[[debugging]]
== Debugging Timeouts
Registered xref:extensions/pre-interrupt-callback.adoc[] extensions are called prior to invoking
`Thread.interrupt()` on the thread that is executing the timed out method. This allows to
inspect the application state and output additional information that might be helpful for
diagnosing the cause of a timeout.
[[debugging-thread-dump]]
=== Thread Dump on Timeout
JUnit registers a default implementation of the xref:extensions/pre-interrupt-callback.adoc[]
extension point that dumps the stacks of all threads to `System.out` if enabled by setting
the `junit.jupiter.execution.timeout.threaddump.enabled`
xref:running-tests/configuration-parameters.adoc[configuration parameter] to `true`.
[[mode]]
== Disable @Timeout Globally
When stepping through your code in a debug session, a fixed timeout limit may influence
the result of the test, e.g. mark the test as failed although all assertions were met.
JUnit Jupiter supports the `junit.jupiter.execution.timeout.mode` configuration parameter
to configure when timeouts are applied. There are three modes: `enabled`, `disabled`,
and `disabled_on_debug`. The default mode is `enabled`.
A VM runtime is considered to run in debug mode when one of its input parameters starts
with `-agentlib:jdwp` or `-Xrunjdwp`.
This heuristic is queried by the `disabled_on_debug` mode.
================================================
FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.0.0.adoc
================================================
[[v6.0.0]]
== 6.0.0
*Date of Release:* September 30, 2025
*Scope:*
* Java 17 and Kotlin 2.1 baseline
* Single version number for Platform, Jupiter, and Vintage
* Use of JSpecify annotations to express nullability
* Integration of JFR functionality in `junit-platform-launcher`
* Removal of `junit-platform-runner` and `junit-platform-jfr`
* Deterministic order of `@Nested` classes
* `MethodOrderer.Default` and `ClassOrderer.Default` for `@Nested` classes
* Inheritance of `@TestMethodOrder` by enclosed `@Nested` classes
* Switch to FastCSV library for `@CsvSource` and `@CsvFileSource`
* Support for using Kotlin `suspend` functions as test methods
* New `--fail-fast` mode for ConsoleLauncher
* Support for cancelling test execution via `CancellationToken`
* Removal of various deprecated behaviors and APIs
For complete details consult the
https://docs.junit.org/6.0.0/release-notes.html[6.0.0 Release Notes] online.
================================================
FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.0.1.adoc
================================================
[[v6.0.1]]
== 6.0.1
*Date of Release:* October 31, 2025
*Scope:* Bug fixes and enhancements since 6.0.0
For a complete list of all _closed_ issues and pull requests for this release, consult the
link:{junit-framework-repo}+/milestone/110?closed=1+[6.0.1] milestone page in the JUnit
repository on GitHub.
[[v6.0.1-junit-platform]]
=== JUnit Platform
[[v6.0.1-junit-platform-bug-fixes]]
==== Bug Fixes
* The `jdk.jfr` package is now an optional import when using the `junit-platform-launcher`
as an OSGi bundle.
[[v6.0.1-junit-platform-new-features-and-improvements]]
==== New Features and Improvements
* Legacy documentation regarding Java 8 compatibility has been removed from the User Guide.
[[v6.0.1-junit-jupiter]]
=== JUnit Jupiter
[[v6.0.1-junit-jupiter-bug-fixes]]
==== Bug Fixes
* A regression introduced in version 6.0.0 caused an exception when using `@CsvSource` or
`@CsvFileSource` if the `delimiter` or `delimiterString` attribute was set to `+++#+++`.
This occurred because `+++#+++` was used as the default comment character without an
option to change it. To resolve this, a new `commentCharacter` attribute has been added
to both annotations. Its default value remains `+++#+++`, but it can now be customized
to avoid conflicts with other control characters.
* Fix `IllegalAccessError` thrown when using the Kotlin-specific `assertDoesNotThrow`
assertion.
* Stop reporting discovery issues for synthetic methods, particularly in conjunction with
Kotlin suspend functions.
* Fix support for test methods with the same signature as package-private methods declared
in super classes in different packages.
[[v6.0.1-junit-jupiter-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
* The `org.junit.jupiter.migrationsupport` module descriptor has been marked as deprecated
for removal.
[[v6.0.1-junit-jupiter-new-features-and-improvements]]
==== New Features and Improvements
* The `@CsvSource` and `@CsvFileSource` annotations now allow specifying a custom comment
character using the new `commentCharacter` attribute.
* Improve error message when `@ParameterizedClass` is used with field injection without
providing enough arguments.
* Allow calling the `TypedArgumentConverter` constructor for `@Nullable T` target types
without having to cast class literals to `Class<@Nullable T>`.
[[v6.0.1-junit-vintage]]
=== JUnit Vintage
[[v6.0.1-junit-vintage-new-features-and-improvements]]
==== New Features and Improvements
* Allow disabling the reporting of discovery issues by the JUnit Vintage engine (including
the one reported for its deprecation) by setting the new
`junit.vintage.discovery.issue.reporting.enabled` configuration parameter to `false`.
================================================
FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.0.2.adoc
================================================
[[v6.0.2]]
== 6.0.2
*Date of Release:* January 6, 2026
*Scope:* Bug fixes and enhancements since 6.0.1
For a complete list of all _closed_ issues and pull requests for this release, consult the
link:{junit-framework-repo}+/milestone/113?closed=1+[6.0.2] milestone page in the JUnit
repository on GitHub.
[[v6.0.2-junit-platform]]
=== JUnit Platform
[[v6.0.2-junit-platform-bug-fixes]]
==== Bug Fixes
* Make `ConsoleLauncher` compatible with JDK 26 by avoiding final field mutations.
* Enable recursive updates when using `NamespacedHierarchicalStore.computeIfAbsent(N, K, Function)`.
This provides parity with the deprecated `NamespacedHierarchicalStore.getOrComputeIfAbsent(N, K, Function)`
[[v6.0.2-junit-jupiter]]
=== JUnit Jupiter
[[v6.0.2-junit-jupiter-bug-fixes]]
==== Bug Fixes
* Allow using `@ResourceLock` on classes annotated with `@ClassTemplate` (or
`@ParameterizedClass`).
* Change API status of recommended method in `ArgumentsProvider` as well as
`ParameterDeclaration(s)` as "maintained" rather than "experimental".
[[v6.0.2-junit-vintage]]
=== JUnit Vintage
No changes.
================================================
FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.0.3.adoc
================================================
[[v6.0.3]]
== 6.0.3
*Date of Release:* February 15, 2026
*Scope:* Bug fixes and enhancements since 6.0.2
For a complete list of all _closed_ issues and pull requests for this release, consult the
link:{junit-framework-repo}+/milestone/116?closed=1+[6.0.3] milestone page in the JUnit
repository on GitHub.
[[v6.0.3-junit-platform]]
=== JUnit Platform
[[v6.0.3-junit-platform-bug-fixes]]
==== Bug Fixes
* A deadlock issue in `NamespacedHierarchicalStore.computeIfAbsent(N, K, Function)` has
been fixed.
[[v6.0.3-junit-jupiter]]
=== JUnit Jupiter
[[v6.0.3-junit-jupiter-bug-fixes]]
==== Bug Fixes
* `@EnabledOnJre` and `@DisabledOnJre` once again work reliably when used with `JRE.OTHER`
in a test running on a Java runtime whose version is higher than the version of the last
`JAVA_*` constant in the `JRE` enum.
[[v6.0.3-junit-vintage]]
=== JUnit Vintage
No changes.
================================================
FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.1.0-M1.adoc
================================================
[[v6.1.0-M1]]
== 6.1.0-M1
*Date of Release:* November 17, 2025
*Scope:*
* New `org.junit.start` module for usage in compact source files
* Execution mode configuration support for dynamic tests and containers
* New parallel test executor implementation
For a complete list of all _closed_ issues and pull requests for this release, consult the
link:{junit-framework-repo}+/milestone/104?closed=1+[6.1.0-M1] milestone page in the JUnit
repository on GitHub.
[[v6.1.0-M1-junit-platform]]
=== JUnit Platform
[[v6.1.0-M1-junit-platform-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
* Deprecate constructors for `ForkJoinPoolHierarchicalTestExecutorService` in favor of the
new `ParallelHierarchicalTestExecutorServiceFactory` that also supports
`WorkerThreadPoolHierarchicalTestExecutorService`.
[[v6.1.0-M1-junit-platform-new-features-and-improvements]]
==== New Features and Improvements
* Support for creating a `ModuleSelector` from a `java.lang.Module` and using
its classloader for test discovery.
* New `WorkerThreadPoolHierarchicalTestExecutorService` implementation used for parallel
test execution that is backed by a regular thread pool rather than a `ForkJoinPool`.
Engine authors should switch to use `ParallelHierarchicalTestExecutorServiceFactory`
rather than instantiating a concrete `HierarchicalTestExecutorService` implementation
for parallel execution directly.
* `OpenTestReportGeneratingListener` now supports redirecting XML events to a socket via
the new `junit.platform.reporting.open.xml.socket` configuration parameter. When set to a
port number, events are sent to `127.0.0.1:` instead of being written to a file.
* Allow implementations of `HierarchicalTestEngine` to specify which nodes require the
global read lock by overriding the `Node.isGlobalReadLockRequired()` method to return
`false`.
[[v6.1.0-M1-junit-jupiter]]
=== JUnit Jupiter
[[v6.1.0-M1-junit-jupiter-new-features-and-improvements]]
==== New Features and Improvements
* Introduce new module `org.junit.start` for writing and running tests. It simplifies
using JUnit in compact source files together with a single `module import` statement.
Find an example at the xref:running-tests/source-launcher.adoc[User Guide].
* Introduce new `dynamicTest(Consumer super Configuration>)` factory method for dynamic
tests. It allows configuring the `ExecutionMode` of the dynamic test in addition to its
display name, test source URI, and executable.
* Introduce new `dynamicContainer(Consumer super Configuration>)` factory method for
dynamic containers. It allows configuring the `ExecutionMode` of the dynamic container
and/or its children in addition to its display name, test source URI, and children.
* Enrich `assertInstanceOf` failure using the test subject `Throwable` as cause. It
results in the stack trace of the test subject `Throwable` to get reported along with
the failure.
* Make implementation of `HierarchicalTestExecutorService` used for parallel test
execution configurable via the new
`junit.jupiter.execution.parallel.config.executor-service` configuration parameter to
in order to add support for `WorkerThreadPoolHierarchicalTestExecutorService`. Please
refer to the
xref:writing-tests/parallel-execution.adoc#config-executor-service[User Guide]
for details.
[[v6.1.0-M1-junit-vintage]]
=== JUnit Vintage
No changes.
================================================
FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.1.0-RC1.adoc
================================================
[[v6.1.0-RC1]]
== 6.1.0-RC1
*Date of Release:* April 25, 2026
*Scope:*
* New `@DefaultLocale` and `@DefaultTimeZone` built-in extensions
* New built-in extension for clearing/setting/restoring system properties
* Configurable deletion strategy for `@TempDir` that allows ignoring failures
* Improved stack trace pruning for assertion failures
* New `org.junit.jupiter.api.Constants` class for referencing configuration parameters
* Improvements to legacy XML reports for parameterized test classes
* New experimental memory cleanup mode for large test suites
For a complete list of all _closed_ issues and pull requests for this release, consult the
link:{junit-framework-repo}+/milestone/112?closed=1+[6.1.0-RC1] milestone page in the JUnit
repository on GitHub.
[[v6.1.0-RC1-junit-platform]]
=== JUnit Platform
[[v6.1.0-RC1-junit-platform-bug-fixes]]
==== Bug Fixes
* A deadlock issue in `NamespacedHierarchicalStore.computeIfAbsent(N, K, Function)` has
been fixed.
* `WorkerThreadPoolHierarchicalTestExecutorService` now rechecks the _done_ condition
before rejecting an extraneous worker.
* Missing precondition checks have been added to `Launcher` implementations.
* Failures to resolve selectors are now propagated by the Suite Engine.
[[v6.1.0-RC1-junit-platform-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
* In the `EngineTestKit`, `Executions.started()` has been deprecated in favor of
`Executions.finished()`, since started executions are always finished.
[[v6.1.0-RC1-junit-platform-new-features-and-improvements]]
==== New Features and Improvements
* `TestDescriptor` implementation requirements have now been clarified in the
corresponding Javadoc.
* The `UniqueId.uniqueIdFormat` field has been removed, reducing the size of `UniqueId`
objects.
* New `selectClasspathResources(String...)` and `selectClasspathResources(List)`
** `Arguments.argumentsFrom(Iterable>)` (alias for `of(Iterable>)`)
** `Arguments.argumentSetFrom(String, Iterable>)`
** `Arguments.toList()` — returns a mutable `List<@Nullable Object>`
* `@EmptySource` now supports `Iterable`, `Iterator`, and `ListIterator`.
* `@Deprecated` factory methods are now excluded in the
xref:writing-tests/parameterized-classes-and-tests.adoc#argument-conversion-implicit-fallback[fallback String-to-Object]
conversion algorithm.
* Internal stack frames are now removed from `AssertionFailedError` stack traces.
* New `trimStacktrace(Class>)` and `retainStackTraceElements(int)` methods in
`AssertionFailureBuilder` which allow user-defined assertions to trim their stack traces.
* Generic inline value classes (such as `kotlin.Result`) can now be used as parameters
in `@ParameterizedTest` methods when `kotlin-reflect` is on the classpath. Note,
however, that primitive-wrapper inline value classes (such as `UInt` or custom value
classes wrapping primitives) are not yet supported.
* Kotlin-specific assertions now include a variant of `assertThrowsExactly` with reified
generics.
* The new `org.junit.jupiter.api.Constants` class provides constants for
xref:running-tests/configuration-parameters.adoc[] specific to the Jupiter engine.
* The `{EmptySource}` annotation now provides a `type` attribute that allows configuring
the type of the empty argument explicitly. This is intended to be used in conjunction
with an `{ArgumentConverter}` that supports the specified type.
[[v6.1.0-RC1-junit-vintage]]
=== JUnit Vintage
No changes.
================================================
FILE: documentation/modules/ROOT/partials/release-notes/release-notes-6.1.0.adoc
================================================
[[v6.1.0]]
== 6.1.0
*Date of Release:* ❓
*Scope:* ❓
For a complete list of all _closed_ issues and pull requests for this release, consult the
link:{junit-framework-repo}+/milestone/119?closed=1+[6.1.0] milestone page in the JUnit
repository on GitHub.
[[v6.1.0-junit-platform]]
=== JUnit Platform
[[v6.1.0-junit-platform-bug-fixes]]
==== Bug Fixes
* `AbstractTestDescriptor.getChildren()` now returns immutable set of children rather than a
merely unmodifiable set.
[[v6.1.0-junit-platform-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
* ❓
[[v6.1.0-junit-platform-new-features-and-improvements]]
==== New Features and Improvements
* ❓
[[v6.1.0-junit-jupiter]]
=== JUnit Jupiter
[[v6.1.0-junit-jupiter-bug-fixes]]
==== Bug Fixes
* ❓
[[v6.1.0-junit-jupiter-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
* ❓
[[v6.1.0-junit-jupiter-new-features-and-improvements]]
==== New Features and Improvements
* ❓
[[v6.1.0-junit-vintage]]
=== JUnit Vintage
[[v6.1.0-junit-vintage-bug-fixes]]
==== Bug Fixes
* ❓
[[v6.1.0-junit-vintage-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
* ❓
[[v6.1.0-junit-vintage-new-features-and-improvements]]
==== New Features and Improvements
* ❓
================================================
FILE: documentation/modules/ROOT/partials/release-notes/release-notes-TEMPLATE.adoc
================================================
// TODO:
//
// 1) Make a copy of this template file, replacing TEMPLATE in the file name with the
// current version (for example, 5.10.0-M1, 5.10.0-RC1, 5.10.0).
// 2) Open the new file for editing.
// 3) Replace all occurrences of VERSION with the current version (for example, 5.10.0-M1,
// 5.10.0-RC1, 5.10.0). The same version must be used in the file name and within the
// file.
// 4) Replace MILESTONE_NUMBER with the appropriate milestone number. This is an integer
// which you can determine via https://github.com/junit-team/junit-framework/milestones/.
// If a GitHub milestone does not yet exist for the given VERSION, you will need to
// create a new GitHub milestone or ask a member of the JUnit team to create it for you.
// 5) 'include:' this new file in ../../pages/release-notes.adoc.
// 6) Delete this entire comment block.
//
[[vVERSION]]
== VERSION
*Date of Release:* ❓
*Scope:* ❓
For a complete list of all _closed_ issues and pull requests for this release, consult the
link:{junit-framework-repo}+/milestone/MILESTONE_NUMBER?closed=1+[VERSION] milestone page in the JUnit
repository on GitHub.
[[vVERSION-junit-platform]]
=== JUnit Platform
[[vVERSION-junit-platform-bug-fixes]]
==== Bug Fixes
* ❓
[[vVERSION-junit-platform-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
* ❓
[[vVERSION-junit-platform-new-features-and-improvements]]
==== New Features and Improvements
* ❓
[[vVERSION-junit-jupiter]]
=== JUnit Jupiter
[[vVERSION-junit-jupiter-bug-fixes]]
==== Bug Fixes
* ❓
[[vVERSION-junit-jupiter-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
* ❓
[[vVERSION-junit-jupiter-new-features-and-improvements]]
==== New Features and Improvements
* ❓
[[vVERSION-junit-vintage]]
=== JUnit Vintage
[[vVERSION-junit-vintage-bug-fixes]]
==== Bug Fixes
* ❓
[[vVERSION-junit-vintage-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
* ❓
[[vVERSION-junit-vintage-new-features-and-improvements]]
==== New Features and Improvements
* ❓
================================================
FILE: documentation/package.json
================================================
{
"devDependencies": {
"antora": "3.2.0-alpha.11"
},
"dependencies": {
"@antora/collector-extension": "1.0.3",
"@antora/lunr-extension": "1.0.0-alpha.13",
"@asciidoctor/tabs": "^1.0.0-beta.6",
"@springio/antora-extensions": "1.14.11",
"@springio/antora-xref-extension": "^1.0.0-alpha.5",
"highlight.js": "11.11.1"
}
}
================================================
FILE: documentation/src/javadoc/junit-overview.html
================================================
This document consists of four sections:
Platform
The JUnit Platform serves as a foundation for launching testing frameworks on the JVM.
It also defines the TestEngine API for developing a testing framework that runs on the
platform. Furthermore, the platform provides a Console Launcher to launch the platform
from the command line and the Suite Engine for running a custom test suite using one
or more test engines on the platform
Jupiter
JUnit Jupiter is the combination of the programming model and extension model for
writing JUnit tests and extensions. The Jupiter subproject provides a TestEngine
for running Jupiter based tests on the platform.
Vintage
JUnit Vintage provides a TestEngine for running JUnit 3 and JUnit 4 based tests on the
platform.
Other Modules
This section lists all modules that are not part of a dedicated section.
Already consulted the JUnit User Guide ?
================================================
FILE: documentation/src/javadoc/junit-stylesheet.css
================================================
/*
* CSS customizations for JUnit
*/
@import url('https://assets.junit.org/fonts/index.css');
:root {
/* body, block and code fonts */
--body-font-family: "Inter Variable", sans-serif;
--block-font-family: "Inter Variable", sans-serif;
--code-font-family: "JetBrains Mono Variable", monospace;
/* Text colors for body and block elements */
--body-text-color: #333;
--block-text-color: #333;
/* Colors for navigation bar and table captions */
--navbar-background-color: #25a162;
/* Background color for subnavigation and various headers */
--subnav-background-color: #e8e8e8;
--subnav-link-color: var(--link-color);
/* Background and text colors for selected tabs and navigation items */
--selected-background-color: #dc524a;
--selected-text-color: #fff;
--selected-link-color: #651410;
/* Background colors for generated tables */
--table-header-color: #eee;
--even-row-color: #fff;
--odd-row-color: #f6f6f6;
/* Text color for page title */
--title-color: #444;
/* Text colors for links */
--link-color: #dc524a;
--link-color-active: #b62b23;
/* Table of contents */
--toc-background-color: #f8f8f8;
--toc-hover-color: #eee;
/* Snippet colors */
--snippet-background-color: #ebecee;
--snippet-text-color: var(--block-text-color);
--snippet-highlight-color: #fcdbd9;
/* Border colors for structural elements and user defined tables */
--border-color: #eee;
--table-border-color: #eee;
/* Highlight color for active search tag target */
--search-tag-highlight-color: #ffff00;
/* Copy button colors and filters */
--button-border-color: #b0b8c8;
}
h1, h2 {
font-family: "Blinker", sans-serif;
font-style: normal !important;
font-weight: 600;
}
dl.notes > dt {
font-size: var(--body-font-size);
}
@media screen and (min-width: 1024px) {
.title {
font-size: 2rem;
}
}
.top-nav a:hover, .bottom-nav a:hover {
text-decoration:underline;
color:inherit;
}
.nav-bar-cell1-rev {
background-color:#fff;
color:#dc524a;
border-radius: 6px;
font-weight: bold;
}
hr {
color: transparent;
border-top: 1px solid var(--border-color);
}
dt {
font-weight: bold;
}
tt,
code,
pre,
.module-signature,
.package-signature,
.type-signature,
.member-signature {
font-variant-ligatures: none;
}
================================================
FILE: documentation/src/main/java/example/domain/Person.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.domain;
import java.time.LocalDate;
public final class Person {
public enum Gender {
F, M
}
private String firstName;
private String lastName;
private Gender gender;
private LocalDate dateOfBirth;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Person(String firstName, String lastName, Gender gender, LocalDate dateOfBirth) {
this(firstName, lastName);
this.gender = gender;
this.dateOfBirth = dateOfBirth;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public Gender getGender() {
return gender;
}
public LocalDate getDateOfBirth() {
return dateOfBirth;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Person other = (Person) obj;
if (firstName == null) {
if (other.firstName != null) {
return false;
}
}
else if (!firstName.equals(other.firstName)) {
return false;
}
if (lastName == null) {
if (other.lastName != null) {
return false;
}
}
else if (!lastName.equals(other.lastName)) {
return false;
}
return true;
}
@Override
public String toString() {
return "Person [firstName=" + firstName + ", lastName=" + lastName + "]";
}
}
================================================
FILE: documentation/src/main/java/example/domain/package-info.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
/**
* Demo domain model.
*/
package example.domain;
================================================
FILE: documentation/src/main/java/example/registration/WebClient.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.registration;
public class WebClient implements AutoCloseable {
public WebResponse get(String string) {
return new WebResponse();
}
@Override
public void close() {
/* no-op for demo */
}
}
================================================
FILE: documentation/src/main/java/example/registration/WebResponse.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.registration;
public class WebResponse {
public int getResponseStatus() {
return 200;
}
}
================================================
FILE: documentation/src/main/java/example/registration/WebServerExtension.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.registration;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class WebServerExtension implements BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext context) {
/* no-op for demo */
}
public String getServerUrl() {
return "https://example.org:8181";
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
public Builder enableSecurity(boolean b) {
return this;
}
public WebServerExtension build() {
return new WebServerExtension();
}
}
}
================================================
FILE: documentation/src/main/java/example/registration/package-info.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
/**
* Demo code for a WebServer extension.
*/
package example.registration;
================================================
FILE: documentation/src/main/java/example/util/Calculator.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.util;
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
public int multiply(int a, int b) {
return a * b;
}
public int divide(int a, int b) {
return a / b;
}
}
================================================
FILE: documentation/src/main/java/example/util/ListWriter.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.util;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class ListWriter {
private final Path file;
public ListWriter(Path file) {
this.file = file;
}
public void write(String... items) throws IOException {
Files.write(file, List.of(String.join(",", items)));
}
}
================================================
FILE: documentation/src/main/java/example/util/StringUtils.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.util;
import static java.util.Objects.requireNonNull;
import org.jspecify.annotations.Nullable;
public class StringUtils {
public static boolean isPalindrome(@Nullable String candidate) {
int length = requireNonNull(candidate).length();
for (int i = 0; i < length / 2; i++) {
if (candidate.charAt(i) != candidate.charAt(length - (i + 1))) {
return false;
}
}
return true;
}
private StringUtils() {
}
}
================================================
FILE: documentation/src/main/java/example/util/package-info.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
/**
* Demo utilities.
*/
package example.util;
================================================
FILE: documentation/src/plantuml/component-diagram.puml
================================================
@startuml
skinparam {
defaultFontName sans-serif
}
package org.junit.jupiter {
[junit-jupiter] as jupiter
[junit-jupiter-api] as jupiter_api
[junit-jupiter-engine] as jupiter_engine
[junit-jupiter-params] as jupiter_params
[junit-jupiter-migrationsupport] as jupiter_migration_support
}
package org.junit.vintage {
[junit-vintage-engine] as vintage_engine
}
package org.junit.platform {
[junit-platform-commons] as commons
[junit-platform-console] as console
[junit-platform-engine] as engine
[junit-platform-launcher] as launcher
[junit-platform-reporting] as reporting
[junit-platform-suite] as suite
[junit-platform-suite-api] as suite_api
[junit-platform-suite-engine] as suite_engine
[junit-platform-testkit] as testkit
}
package "JUnit 4" {
[junit:junit] as junit4
}
package org.opentest4j {
[opentest4j]
}
package org.opentest4j.reporting {
[open-test-reporting-tooling-spi] as otr_tooling_spi
}
package org.apiguardian {
[apiguardian-api] as apiguardian
note bottom of apiguardian #white
All artifacts except
opentest4j and junit:junit
have a dependency on this
artifact. The edges have
been omitted from this
diagram for the sake of
readability.
endnote
}
jupiter ..> jupiter_api
jupiter ..> jupiter_params
jupiter ..> jupiter_engine
jupiter_api ....> opentest4j
jupiter_api ...> commons
jupiter_engine ...> engine
jupiter_engine ..> jupiter_api
jupiter_params ..> jupiter_api
jupiter_migration_support ..> jupiter_api
jupiter_migration_support ...> junit4
console ..> launcher
console ..> reporting
launcher ..> engine
engine ....> opentest4j
engine ..> commons
reporting ..> launcher
reporting ......> otr_tooling_spi
suite ..> suite_api
suite ..> suite_engine
suite_engine ..> launcher
suite_engine ..> suite_api
testkit ....> opentest4j
testkit ..> launcher
vintage_engine ...> engine
vintage_engine ..> junit4
@enduml
================================================
FILE: documentation/src/plantuml/junit-platform-suite-engine-diagram.puml
================================================
@startuml
object "IDEs"
object "Build Tools"
object "Console Launcher"
object "JUnit Platform"
object "Suite Test Engine"
object "@Suite annotated class A"
object "@Suite annotated class B"
object "JUnit Platform (A)"
object "JUnit Platform (B)"
object "Jupiter Test Engine (A)"
object "Jupiter Test Engine (B)"
object "Tests in package A"
object "Tests in package B"
"IDEs" --> "JUnit Platform"
"Build Tools" --> "JUnit Platform"
"Console Launcher" --> "JUnit Platform" : requests discovery and execution
"JUnit Platform" --> "Suite Test Engine": forwards request
"Suite Test Engine" --> "@Suite annotated class A"
"@Suite annotated class A" --> "JUnit Platform (A)"
"JUnit Platform (A)" --> "Jupiter Test Engine (A)"
"Jupiter Test Engine (A)" --> "Tests in package A"
"Suite Test Engine" --> "@Suite annotated class B" : discovers and executes
"@Suite annotated class B" --> "JUnit Platform (B)" : requests discovery and execution
"JUnit Platform (B)" --> "Jupiter Test Engine (B)" : forwards request
"Jupiter Test Engine (B)" --> "Tests in package B" : discovers and executes
@enduml
================================================
FILE: documentation/src/plantuml/junit-platform-suite-engine-duplicate-test-execution-diagram.puml
================================================
@startuml
object "IDEs"
object "Build Tools"
object "Console Launcher"
object "JUnit Platform"
together {
object "Suite Test Engine"
object "Jupiter Test Engine"
}
object "@Suite annotated class"
object "JUnit Platform (@Suite)"
object "Jupiter Test Engine (@Suite)"
together {
object "Example Test A"
object "Example Test A (@Suite)"
}
"IDEs" --> "JUnit Platform"
"Build Tools" --> "JUnit Platform"
"Console Launcher" --> "JUnit Platform" : requests discovery and execution
"JUnit Platform" --> "Suite Test Engine"
"Suite Test Engine" --> "@Suite annotated class" : discovers and executes
"@Suite annotated class" --> "JUnit Platform (@Suite)" : requests discovery and execution
"JUnit Platform (@Suite)" --> "Jupiter Test Engine (@Suite)" : forwards request
"Jupiter Test Engine (@Suite)" --> "Example Test A (@Suite)" : discovers and executes
"JUnit Platform" --> "Jupiter Test Engine": forwards request
"Jupiter Test Engine" --> "Example Test A" #line:red;line.bold;text:red : also discovers and executes!
@enduml
================================================
FILE: documentation/src/plantuml/launcher-api-diagram.puml
================================================
@startuml
object "IDEs"
object "Build Tools"
object "Console Launcher"
object "JUnit Platform"
object "Jupiter Test Engine"
object "Cucumber Test Engine"
object "Spock Test Engine"
object "Test Classes"
object "Feature Files"
object "Specifications"
"IDEs" --> "JUnit Platform"
"Build Tools" --> "JUnit Platform"
"Console Launcher" --> "JUnit Platform" : requests discovery and execution
"JUnit Platform" --> "Jupiter Test Engine"
"Jupiter Test Engine" --> "Test Classes"
"JUnit Platform" --> "Cucumber Test Engine"
"Cucumber Test Engine" --> "Feature Files"
"JUnit Platform" --> "Spock Test Engine": forwards request
"Spock Test Engine" --> "Specifications" : discovers and executes
@enduml
================================================
FILE: documentation/src/test/java/example/AssertJAssertionsDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import static org.assertj.core.api.Assertions.assertThat;
import example.util.Calculator;
import org.junit.jupiter.api.Test;
class AssertJAssertionsDemo {
private final Calculator calculator = new Calculator();
@Test
void assertWithAssertJ() {
assertThat(calculator.subtract(4, 1)).isEqualTo(3);
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/AssertionsDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// @formatter:off
// tag::user_guide[]
import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.concurrent.CountDownLatch;
import example.domain.Person;
import example.util.Calculator;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
class AssertionsDemo {
private final Calculator calculator = new Calculator();
private final Person person = new Person("Jane", "Doe");
@Test
void standardAssertions() {
assertEquals(2, calculator.add(1, 1));
assertEquals(4, calculator.multiply(2, 2),
"The optional failure message is now the last parameter");
// Lazily evaluates generateFailureMessage('a','b').
assertTrue('a' < 'b', () -> generateFailureMessage('a','b'));
}
@Test
void groupedAssertions() {
// In a grouped assertion all assertions are executed, and all
// failures will be reported together.
assertAll("person",
() -> assertEquals("Jane", person.getFirstName()),
() -> assertEquals("Doe", person.getLastName())
);
}
@Test
void dependentAssertions() {
// Within a code block, if an assertion fails the
// subsequent code in the same block will be skipped.
assertAll("properties",
() -> {
String firstName = person.getFirstName();
assertNotNull(firstName);
// Executed only if the previous assertion is valid.
assertAll("first name",
() -> assertTrue(firstName.startsWith("J")),
() -> assertTrue(firstName.endsWith("e"))
);
},
() -> {
// Grouped assertion, so processed independently
// of results of first name assertions.
String lastName = person.getLastName();
assertNotNull(lastName);
// Executed only if the previous assertion is valid.
assertAll("last name",
() -> assertTrue(lastName.startsWith("D")),
() -> assertTrue(lastName.endsWith("e"))
);
}
);
}
// end::user_guide[]
@extensions.DisabledOnOpenJ9
// tag::user_guide[]
@Test
void exceptionTesting() {
Exception exception = assertThrows(ArithmeticException.class, () ->
calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage());
}
// end::user_guide[]
@Tag("timeout")
// tag::user_guide[]
@Test
void timeoutNotExceeded() {
// The following assertion succeeds.
assertTimeout(ofMinutes(2), () -> {
// Perform task that takes less than 2 minutes.
});
}
// end::user_guide[]
@Tag("timeout")
// tag::user_guide[]
@Test
void timeoutNotExceededWithResult() {
// The following assertion succeeds, and returns the supplied object.
String actualResult = assertTimeout(ofMinutes(2), () -> {
return "a result";
});
assertEquals("a result", actualResult);
}
// end::user_guide[]
@Tag("timeout")
// tag::user_guide[]
@Test
void timeoutNotExceededWithMethod() {
// The following assertion invokes a method reference and returns an object.
String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting);
assertEquals("Hello, World!", actualGreeting);
}
// end::user_guide[]
@Tag("timeout")
@extensions.ExpectToFail
// tag::user_guide[]
@Test
void timeoutExceeded() {
// The following assertion fails with an error message similar to:
// execution exceeded timeout of 10 ms by 91 ms
assertTimeout(ofMillis(10), () -> {
// Simulate task that takes more than 10 ms.
Thread.sleep(100);
});
}
// end::user_guide[]
@Tag("timeout")
@extensions.ExpectToFail
// tag::user_guide[]
@Test
void timeoutExceededWithPreemptiveTermination() {
// The following assertion fails with an error message similar to:
// execution timed out after 10 ms
assertTimeoutPreemptively(ofMillis(10), () -> {
// Simulate task that takes more than 10 ms.
new CountDownLatch(1).await();
});
}
private static String greeting() {
return "Hello, World!";
}
private static String generateFailureMessage(char a, char b) {
return "Assertion messages can be lazily evaluated -- "
+ "to avoid constructing complex messages unnecessarily." + (a < b);
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/AssumptionsDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// @formatter:off
// tag::user_guide[]
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumingThat;
import example.util.Calculator;
import org.junit.jupiter.api.Test;
class AssumptionsDemo {
private final Calculator calculator = new Calculator();
@Test
void testOnlyOnCiServer() {
assumeTrue("CI".equals(System.getenv("ENV")));
// remainder of test
}
@Test
void testOnlyOnDeveloperWorkstation() {
assumeTrue("DEV".equals(System.getenv("ENV")),
() -> "Aborting test: not on developer workstation");
// remainder of test
}
@Test
void testInAllEnvironments() {
assumingThat("CI".equals(System.getenv("ENV")),
() -> {
// perform these assertions only on the CI server
assertEquals(2, calculator.divide(4, 2));
});
// perform these assertions in all environments
assertEquals(42, calculator.multiply(6, 7));
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/AutoCloseDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.jupiter.api.Assertions.assertEquals;
import example.registration.WebClient;
import org.junit.jupiter.api.AutoClose;
import org.junit.jupiter.api.Test;
// tag::user_guide_example[]
class AutoCloseDemo {
@AutoClose // <1>
WebClient webClient = new WebClient(); // <2>
String serverUrl = // specify server URL ...
// end::user_guide_example[]
"https://localhost";
// tag::user_guide_example[]
@Test
void getProductList() {
// Use WebClient to connect to web server and verify response
assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus());
}
}
// end::user_guide_example[]
================================================
FILE: documentation/src/test/java/example/BeforeAndAfterSuiteDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import org.junit.platform.suite.api.AfterSuite;
import org.junit.platform.suite.api.BeforeSuite;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
//tag::user_guide[]
@Suite
@SelectPackages("example")
class BeforeAndAfterSuiteDemo {
@BeforeSuite
static void beforeSuite() {
// executes before the test suite
}
@AfterSuite
static void afterSuite() {
// executes after the test suite
}
}
//end::user_guide[]
================================================
FILE: documentation/src/test/java/example/ClassTemplateDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.ClassTemplate;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ClassTemplateInvocationContext;
import org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
// tag::user_guide[]
@ClassTemplate
@ExtendWith(ClassTemplateDemo.MyClassTemplateInvocationContextProvider.class)
class ClassTemplateDemo {
static final List WELL_KNOWN_FRUITS
// tag::custom_line_break[]
= List.of("apple", "banana", "lemon");
//end::user_guide[]
@Nullable
//tag::user_guide[]
private String fruit;
@Test
void notNull() {
assertNotNull(fruit);
}
@Test
void wellKnown() {
assertTrue(WELL_KNOWN_FRUITS.contains(fruit));
}
// end::user_guide[]
static
// tag::user_guide[]
public class MyClassTemplateInvocationContextProvider
// tag::custom_line_break[]
implements ClassTemplateInvocationContextProvider {
@Override
public boolean supportsClassTemplate(ExtensionContext context) {
return true;
}
@Override
public Stream
// tag::custom_line_break[]
provideClassTemplateInvocationContexts(ExtensionContext context) {
return Stream.of(invocationContext("apple"), invocationContext("banana"));
}
private ClassTemplateInvocationContext invocationContext(String parameter) {
return new ClassTemplateInvocationContext() {
@Override
public String getDisplayName(int invocationIndex) {
return parameter;
}
// end::user_guide[]
@SuppressWarnings("Convert2Lambda")
// tag::user_guide[]
@Override
public List getAdditionalExtensions() {
return List.of(new TestInstancePostProcessor() {
@Override
public void postProcessTestInstance(
// tag::custom_line_break[]
Object testInstance, ExtensionContext context) {
((ClassTemplateDemo) testInstance).fruit = parameter;
}
});
}
};
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/ConditionalTestExecutionDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.jupiter.api.condition.JRE.JAVA_17;
import static org.junit.jupiter.api.condition.JRE.JAVA_18;
import static org.junit.jupiter.api.condition.JRE.JAVA_19;
import static org.junit.jupiter.api.condition.JRE.JAVA_21;
import static org.junit.jupiter.api.condition.JRE.JAVA_25;
import static org.junit.jupiter.api.condition.OS.LINUX;
import static org.junit.jupiter.api.condition.OS.MAC;
import static org.junit.jupiter.api.condition.OS.WINDOWS;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledForJreRange;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.junit.jupiter.api.condition.EnabledInNativeImage;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.EnabledOnOs;
class ConditionalTestExecutionDemo {
// tag::user_guide_os[]
@Test
@EnabledOnOs(MAC)
void onlyOnMacOs() {
// ...
}
@TestOnMac
void testOnMac() {
// ...
}
@Test
@EnabledOnOs({ LINUX, MAC })
void onLinuxOrMac() {
// ...
}
@Test
@DisabledOnOs(WINDOWS)
void notOnWindows() {
// ...
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Test
@EnabledOnOs(MAC)
@interface TestOnMac {
}
// end::user_guide_os[]
// tag::user_guide_architecture[]
@Test
@EnabledOnOs(architectures = "aarch64")
void onAarch64() {
// ...
}
@Test
@DisabledOnOs(architectures = "x86_64")
void notOnX86_64() {
// ...
}
@Test
@EnabledOnOs(value = MAC, architectures = "aarch64")
void onNewMacs() {
// ...
}
@Test
@DisabledOnOs(value = MAC, architectures = "aarch64")
void notOnNewMacs() {
// ...
}
// end::user_guide_architecture[]
// tag::user_guide_jre[]
@Test
@EnabledOnJre(JAVA_17)
void onlyOnJava17() {
// ...
}
@Test
@EnabledOnJre({ JAVA_17, JAVA_21 })
void onJava17And21() {
// ...
}
@Test
@EnabledForJreRange(min = JAVA_21, max = JAVA_25)
void fromJava21To25() {
// ...
}
@Test
@EnabledForJreRange(min = JAVA_21)
void onJava21ndHigher() {
// ...
}
@Test
@EnabledForJreRange(max = JAVA_18)
void fromJava17To18() {
// ...
}
@Test
@DisabledOnJre(JAVA_19)
void notOnJava19() {
// ...
}
@Test
@DisabledForJreRange(min = JAVA_17, max = JAVA_17)
void notFromJava17To19() {
// ...
}
@Test
@DisabledForJreRange(min = JAVA_19)
void notOnJava19AndHigher() {
// ...
}
@Test
@DisabledForJreRange(max = JAVA_18)
void notFromJava17To18() {
// ...
}
// end::user_guide_jre[]
// tag::user_guide_jre_arbitrary_versions[]
@Test
@EnabledOnJre(versions = 26)
void onlyOnJava26() {
// ...
}
@Test
@EnabledOnJre(versions = { 25, 26 })
// Can also be expressed as follows.
// @EnabledOnJre(value = JAVA_25, versions = 26)
void onJava25And26() {
// ...
}
@Test
@EnabledForJreRange(minVersion = 26)
void onJava26AndHigher() {
// ...
}
@Test
@EnabledForJreRange(minVersion = 25, maxVersion = 27)
// Can also be expressed as follows.
// @EnabledForJreRange(min = JAVA_25, maxVersion = 27)
void fromJava25To27() {
// ...
}
@Test
@DisabledOnJre(versions = 26)
void notOnJava26() {
// ...
}
@Test
@DisabledOnJre(versions = { 25, 26 })
// Can also be expressed as follows.
// @DisabledOnJre(value = JAVA_25, versions = 26)
void notOnJava25And26() {
// ...
}
@Test
@DisabledForJreRange(minVersion = 26)
void notOnJava26AndHigher() {
// ...
}
@Test
@DisabledForJreRange(minVersion = 25, maxVersion = 27)
// Can also be expressed as follows.
// @DisabledForJreRange(min = JAVA_25, maxVersion = 27)
void notFromJava25To27() {
// ...
}
// end::user_guide_jre_arbitrary_versions[]
// tag::user_guide_native[]
@Test
@EnabledInNativeImage
void onlyWithinNativeImage() {
// ...
}
@Test
@DisabledInNativeImage
void neverWithinNativeImage() {
// ...
}
// end::user_guide_native[]
// tag::user_guide_system_property[]
@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
void onlyOn64BitArchitectures() {
// ...
}
@Test
@DisabledIfSystemProperty(named = "ci-server", matches = "true")
void notOnCiServer() {
// ...
}
// end::user_guide_system_property[]
// tag::user_guide_environment_variable[]
@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server")
void onlyOnStagingServer() {
// ...
}
@Test
@DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*")
void notOnDeveloperWorkstation() {
// ...
}
// end::user_guide_environment_variable[]
// tag::user_guide_custom[]
@Test
@EnabledIf("customCondition")
void enabled() {
// ...
}
@Test
@DisabledIf("customCondition")
void disabled() {
// ...
}
boolean customCondition() {
return true;
}
// end::user_guide_custom[]
}
================================================
FILE: documentation/src/test/java/example/CustomLauncherInterceptor.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import org.junit.platform.launcher.LauncherInterceptor;
public class CustomLauncherInterceptor implements LauncherInterceptor {
private final URLClassLoader customClassLoader;
public CustomLauncherInterceptor() throws Exception {
ClassLoader parent = Thread.currentThread().getContextClassLoader();
customClassLoader = new URLClassLoader(new URL[] { URI.create("some.jar").toURL() }, parent);
}
@Override
public T intercept(Invocation invocation) {
Thread currentThread = Thread.currentThread();
ClassLoader originalClassLoader = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(customClassLoader);
try {
return invocation.proceed();
}
finally {
currentThread.setContextClassLoader(originalClassLoader);
}
}
@Override
public void close() {
try {
customClassLoader.close();
}
catch (IOException e) {
throw new UncheckedIOException("Failed to close custom class loader", e);
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/CustomTestEngine.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import org.junit.platform.engine.EngineDiscoveryRequest;
import org.junit.platform.engine.ExecutionRequest;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestEngine;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.descriptor.EngineDescriptor;
/**
* This is a no-op {@link TestEngine} that is only
* used to make examples compile.
*/
class CustomTestEngine implements TestEngine {
@Override
public String getId() {
return "custom-test-engine";
}
@Override
public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) {
return new EngineDescriptor(UniqueId.forEngine(getId()), "Custom Test Engine");
}
@Override
public void execute(ExecutionRequest request) {
}
}
================================================
FILE: documentation/src/test/java/example/DefaultLocaleTimezoneExtensionDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.ZoneOffset;
import java.util.Locale;
import java.util.TimeZone;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.util.DefaultLocale;
import org.junit.jupiter.api.util.DefaultTimeZone;
import org.junit.jupiter.api.util.LocaleProvider;
import org.junit.jupiter.api.util.TimeZoneProvider;
public class DefaultLocaleTimezoneExtensionDemo {
// tag::default_locale_language[]
@Test
@DefaultLocale("zh-Hant-TW")
void test_with_language() {
assertThat(Locale.getDefault()).isEqualTo(Locale.forLanguageTag("zh-Hant-TW"));
}
// end::default_locale_language[]
// tag::default_locale_language_alternatives[]
@Test
@DefaultLocale(language = "en")
void test_with_language_only() {
assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("en").build());
}
@Test
@DefaultLocale(language = "en", country = "EN")
void test_with_language_and_country() {
assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("en").setRegion("EN").build());
}
@Test
@DefaultLocale(language = "ja", country = "JP", variant = "japanese")
void test_with_language_and_country_and_vairant() {
assertThat(Locale.getDefault()).isEqualTo(
new Locale.Builder().setLanguage("ja").setRegion("JP").setVariant("japanese").build());
}
// end::default_locale_language_alternatives[]
@Nested
// tag::default_locale_class_level[]
@DefaultLocale(language = "fr")
class MyLocaleTests {
@Test
void test_with_class_level_configuration() {
assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("fr").build());
}
@Test
@DefaultLocale(language = "en")
void test_with_method_level_configuration() {
assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("en").build());
}
}
// end::default_locale_class_level[]
// tag::default_locale_with_provider[]
@Test
@DefaultLocale(localeProvider = EnglishProvider.class)
void test_with_locale_provider() {
assertThat(Locale.getDefault()).isEqualTo(new Locale.Builder().setLanguage("en").build());
}
static class EnglishProvider implements LocaleProvider {
@Override
public Locale get() {
return Locale.ENGLISH;
}
}
// end::default_locale_with_provider[]
// tag::default_timezone_zone[]
@Test
@DefaultTimeZone("CET")
void test_with_short_zone_id() {
assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("CET"));
}
@Test
@DefaultTimeZone("Africa/Juba")
void test_with_long_zone_id() {
assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("Africa/Juba"));
}
// end::default_timezone_zone[]
@Nested
// tag::default_timezone_class_level[]
@DefaultTimeZone("CET")
class MyTimeZoneTests {
@Test
void test_with_class_level_configuration() {
assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("CET"));
}
@Test
@DefaultTimeZone("Africa/Juba")
void test_with_method_level_configuration() {
assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("Africa/Juba"));
}
}
// end::default_timezone_class_level[]
// tag::default_time_zone_with_provider[]
@Test
@DefaultTimeZone(timeZoneProvider = UtcTimeZoneProvider.class)
void test_with_time_zone_provider() {
assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("UTC"));
}
static class UtcTimeZoneProvider implements TimeZoneProvider {
@Override
public TimeZone get() {
return TimeZone.getTimeZone(ZoneOffset.UTC);
}
}
// end::default_time_zone_with_provider[]
}
================================================
FILE: documentation/src/test/java/example/DisabledClassDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Disabled until bug #99 has been fixed")
class DisabledClassDemo {
@Test
void testWillBeSkipped() {
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/DisabledTestsDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class DisabledTestsDemo {
@Disabled("Disabled until bug #42 has been resolved")
@Test
void testWillBeSkipped() {
}
@Test
void testWillBeExecuted() {
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/DisplayNameDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@DisplayName("A special test case")
class DisplayNameDemo {
@Test
@DisplayName("Custom test name containing spaces")
void testWithDisplayNameContainingSpaces() {
}
@Test
@DisplayName("╯°□°)╯")
void testWithDisplayNameContainingSpecialCharacters() {
}
@Test
@DisplayName("😱")
void testWithDisplayNameContainingEmoji() {
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/DisplayNameGeneratorDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences.SentenceFragment;
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
import org.junit.jupiter.api.IndicativeSentencesGeneration;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class DisplayNameGeneratorDemo {
@Nested
// tag::user_guide_replace_underscores[]
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class A_year_is_not_supported {
@Test
void if_it_is_zero() {
}
@DisplayName("A negative value for year is not supported by the leap year computation.")
@ParameterizedTest(name = "For example, year {0} is not supported.")
@ValueSource(ints = { -1, -4 })
void if_it_is_negative(int year) {
}
}
// end::user_guide_replace_underscores[]
@Nested
// tag::user_guide_indicative_sentences[]
@IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class)
class A_year_is_a_leap_year {
@Test
void if_it_is_divisible_by_4_but_not_by_100() {
}
@ParameterizedTest(name = "Year {0} is a leap year.")
@ValueSource(ints = { 2016, 2020, 2048 })
void if_it_is_one_of_the_following_years(int year) {
}
}
// end::user_guide_indicative_sentences[]
@Nested
// tag::user_guide_custom_sentence_fragments[]
@SentenceFragment("A year is a leap year")
@IndicativeSentencesGeneration
class LeapYearTests {
@SentenceFragment("if it is divisible by 4 but not by 100")
@Test
void divisibleBy4ButNotBy100() {
}
@SentenceFragment("if it is one of the following years")
@ParameterizedTest(name = "{0}")
@ValueSource(ints = { 2016, 2020, 2048 })
void validLeapYear(int year) {
}
}
// end::user_guide_custom_sentence_fragments[]
}
================================================
FILE: documentation/src/test/java/example/DocumentationTestSuite.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import org.junit.platform.suite.api.ExcludeTags;
import org.junit.platform.suite.api.IncludeClassNamePatterns;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
/**
* Logging Configuration
*
* In order for our log4j2 configuration to be used in an IDE, you must
* set the following system property before running any tests — for
* example, in Run Configurations in Eclipse.
*
*
* -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
*
*
* @since 5.0
*/
@Suite
@SelectPackages("example")
@IncludeClassNamePatterns(".+(Tests|Demo)$")
@ExcludeTags("exclude")
class DocumentationTestSuite {
}
================================================
FILE: documentation/src/test/java/example/DynamicTestsDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import static example.util.StringUtils.isPalindrome;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT;
import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import example.util.Calculator;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.function.ThrowingConsumer;
import org.junit.jupiter.api.parallel.Execution;
// end::user_guide[]
// @formatter:off
// tag::user_guide[]
class DynamicTestsDemo {
private final Calculator calculator = new Calculator();
// This method will not be executed but produce a warning
@TestFactory
// end::user_guide[]
@Tag("exclude")
DynamicTest dummy() { return dynamicTest("dummy", () -> {}); }
// tag::user_guide[]
List dynamicTestsWithInvalidReturnType() {
return Arrays.asList("Hello");
}
@TestFactory
Collection dynamicTestsFromCollection() {
return Arrays.asList(
dynamicTest("1st dynamic test", () -> assertTrue(isPalindrome("madam"))),
dynamicTest("2nd dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
);
}
@TestFactory
Iterable dynamicTestsFromIterable() {
return Arrays.asList(
dynamicTest("3rd dynamic test", () -> assertTrue(isPalindrome("madam"))),
dynamicTest("4th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
);
}
@TestFactory
Iterator dynamicTestsFromIterator() {
return Arrays.asList(
dynamicTest("5th dynamic test", () -> assertTrue(isPalindrome("madam"))),
dynamicTest("6th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
).iterator();
}
@TestFactory
DynamicTest[] dynamicTestsFromArray() {
return new DynamicTest[] {
dynamicTest("7th dynamic test", () -> assertTrue(isPalindrome("madam"))),
dynamicTest("8th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
};
}
@TestFactory
Stream dynamicTestsFromStream() {
return Stream.of("racecar", "radar", "mom", "dad")
.map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))));
}
@TestFactory
Stream dynamicTestsFromIntStream() {
// Generates tests for the first 10 even integers.
return IntStream.iterate(0, n -> n + 2).limit(10)
.mapToObj(n -> dynamicTest("test" + n, () -> assertEquals(0, n % 2)));
}
@TestFactory
Stream generateRandomNumberOfTests() {
// Generates random positive integers between 0 and 100 until
// a number evenly divisible by 7 is encountered.
Iterator inputGenerator = new Iterator<>() {
Random random = new Random();
// end::user_guide[]
{
// Use fixed seed to always produce the same number of tests for execution on the CI server
random = new Random(23);
}
// tag::user_guide[]
int current;
@Override
public boolean hasNext() {
current = random.nextInt(100);
return current % 7 != 0;
}
@Override
public Integer next() {
return current;
}
};
// Generates display names like: input:5, input:37, input:85, etc.
Function displayNameGenerator = (input) -> "input:" + input;
// Executes tests based on the current input value.
ThrowingConsumer testExecutor = (input) -> assertTrue(input % 7 != 0);
// Returns a stream of dynamic tests.
return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor);
}
@TestFactory
Stream dynamicTestsFromStreamFactoryMethod() {
// Stream of palindromes to check
Stream inputStream = Stream.of("racecar", "radar", "mom", "dad");
// Generates display names like: racecar is a palindrome
Function displayNameGenerator = text -> text + " is a palindrome";
// Executes tests based on the current input value.
ThrowingConsumer testExecutor = text -> assertTrue(isPalindrome(text));
// Returns a stream of dynamic tests.
return DynamicTest.stream(inputStream, displayNameGenerator, testExecutor);
}
@TestFactory
Stream dynamicTestsWithContainers() {
return Stream.of("A", "B", "C")
.map(input -> dynamicContainer("Container " + input, Stream.of(
dynamicTest("not null", () -> assertNotNull(input)),
dynamicContainer("properties", Stream.of(
dynamicTest("length > 0", () -> assertTrue(input.length() > 0)),
dynamicTest("not empty", () -> assertFalse(input.isEmpty()))
))
)));
}
// end::user_guide[]
// tag::execution_mode[]
@TestFactory
@Execution(CONCURRENT) // <1>
Stream dynamicTestsWithConfiguredExecutionMode() {
return Stream.of("A", "B", "C")
.map(input ->
dynamicContainer(outer -> outer
.displayName("Container " + input)
.children(
dynamicTest(config -> config
.displayName("not null")
.executionMode(SAME_THREAD) // <2>
.executable(() -> assertNotNull(input))
),
dynamicContainer(inner -> inner
.displayName("properties")
.executionMode(CONCURRENT) // <3>
.childExecutionMode(SAME_THREAD) // <4>
.children(
dynamicTest(config -> config
.displayName("length > 0")
.executionMode(CONCURRENT) // <5>
.executable(() -> assertTrue(input.length() > 0))
),
dynamicTest(config -> config
.displayName("not empty")
.executable(() -> assertFalse(input.isEmpty()))
)
)
)
)
)
);
}
// end::execution_mode[]
// tag::user_guide[]
@TestFactory
DynamicNode dynamicNodeSingleTest() {
return dynamicTest("'pop' is a palindrome", () -> assertTrue(isPalindrome("pop")));
}
@TestFactory
DynamicNode dynamicNodeSingleContainer() {
return dynamicContainer("palindromes",
Stream.of("racecar", "radar", "mom", "dad")
.map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text)))
));
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/DynamicTestsNamedDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import static example.util.StringUtils.isPalindrome;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Named.named;
import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.NamedExecutable;
import org.junit.jupiter.api.TestFactory;
public class DynamicTestsNamedDemo {
@TestFactory
Stream dynamicTestsFromStreamFactoryMethodWithNames() {
// Stream of palindromes to check
// end::user_guide[]
// @formatter:off
// tag::user_guide[]
var inputStream = Stream.of(
named("racecar is a palindrome", "racecar"),
named("radar is also a palindrome", "radar"),
named("mom also seems to be a palindrome", "mom"),
named("dad is yet another palindrome", "dad")
);
// end::user_guide[]
// @formatter:on
// tag::user_guide[]
// Returns a stream of dynamic tests.
return DynamicTest.stream(inputStream, text -> assertTrue(isPalindrome(text)));
}
@TestFactory
Stream dynamicTestsFromStreamFactoryMethodWithNamedExecutables() {
// Stream of palindromes to check
// end::user_guide[]
// @formatter:off
// tag::user_guide[]
var inputStream = Stream.of("racecar", "radar", "mom", "dad")
.map(PalindromeNamedExecutable::new);
// end::user_guide[]
// @formatter:on
// tag::user_guide[]
// Returns a stream of dynamic tests based on NamedExecutables.
return DynamicTest.stream(inputStream);
}
record PalindromeNamedExecutable(String text) implements NamedExecutable {
@Override
public String getName() {
return "'%s' is a palindrome".formatted(text);
}
@Override
public void execute() {
assertTrue(isPalindrome(text));
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/ExampleTestCase.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import example.util.Calculator;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(OrderAnnotation.class)
public class ExampleTestCase {
private final Calculator calculator = new Calculator();
@Test
@Disabled("for demonstration purposes")
@Order(1)
void skippedTest() {
// skipped ...
}
@Test
@Order(2)
void succeedingTest() {
assertEquals(42, calculator.multiply(6, 7));
}
@Test
@Order(3)
void abortedTest() {
assumeTrue("abc".contains("Z"), "abc does not contain Z");
// aborted ...
}
@Test
@Order(4)
void failingTest() {
// The following throws an ArithmeticException: "/ by zero"
calculator.divide(1, 0);
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/ExplicitExecutionModeDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
//tag::user_guide[]
@Execution(ExecutionMode.CONCURRENT)
class ExplicitExecutionModeDemo {
@Test
void testA() {
// concurrent
}
@Test
@Execution(ExecutionMode.SAME_THREAD)
void testB() {
// overrides to same_thread
}
}
//end::user_guide[]
================================================
FILE: documentation/src/test/java/example/ExternalCustomConditionDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide_external_custom_condition[]
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
class ExternalCustomConditionDemo {
@Test
@EnabledIf("example.ExternalCondition#customCondition")
void enabled() {
// ...
}
}
class ExternalCondition {
static boolean customCondition() {
return true;
}
}
// end::user_guide_external_custom_condition[]
================================================
FILE: documentation/src/test/java/example/ExternalFieldSourceDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import java.util.List;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.FieldSource;
class ExternalFieldSourceDemo {
// tag::external_field_FieldSource_example[]
@ParameterizedTest
@FieldSource("example.FruitUtils#tropicalFruits")
void testWithExternalFieldSource(String tropicalFruit) {
// test with tropicalFruit
}
// end::external_field_FieldSource_example[]
}
class FruitUtils {
public static final List tropicalFruits = List.of("pineapple", "kiwi");
}
================================================
FILE: documentation/src/test/java/example/ExternalMethodSourceDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::external_MethodSource_example[]
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
class ExternalMethodSourceDemo {
@ParameterizedTest
@MethodSource("example.StringsProviders#tinyStrings")
void testWithExternalMethodSource(String tinyString) {
// test with tiny string
}
}
class StringsProviders {
static Stream tinyStrings() {
return Stream.of(".", "oo", "OOO");
}
}
// end::external_MethodSource_example[]
================================================
FILE: documentation/src/test/java/example/Fast.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Tag;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
public @interface Fast {
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/FastTest.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
@Test
public @interface FastTest {
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/FirstCustomEngine.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
//tag::user_guide[]
import static java.net.InetAddress.getLoopbackAddress;
import static org.junit.platform.engine.TestExecutionResult.successful;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import org.jspecify.annotations.Nullable;
import org.junit.platform.engine.EngineDiscoveryRequest;
import org.junit.platform.engine.ExecutionRequest;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestEngine;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.descriptor.EngineDescriptor;
import org.junit.platform.engine.support.store.Namespace;
import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
/**
* First custom test engine implementation.
*/
public class FirstCustomEngine implements TestEngine {
//end::user_guide[]
@Nullable
//tag::user_guide[]
public ServerSocket socket;
@Override
public String getId() {
return "first-custom-test-engine";
}
//end::user_guide[]
@Nullable
//tag::user_guide[]
public ServerSocket getSocket() {
return this.socket;
}
@Override
public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) {
return new EngineDescriptor(uniqueId, "First Custom Test Engine");
}
@Override
public void execute(ExecutionRequest request) {
request.getEngineExecutionListener()
// tag::custom_line_break[]
.executionStarted(request.getRootTestDescriptor());
NamespacedHierarchicalStore store = request.getStore();
socket = store.computeIfAbsent(Namespace.GLOBAL, "serverSocket", key -> {
try {
return new ServerSocket(0, 50, getLoopbackAddress());
}
catch (IOException e) {
throw new UncheckedIOException("Failed to start ServerSocket", e);
}
}, ServerSocket.class);
request.getEngineExecutionListener()
// tag::custom_line_break[]
.executionFinished(request.getRootTestDescriptor(), successful());
}
}
//end::user_guide[]
================================================
FILE: documentation/src/test/java/example/HttpServerDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import com.sun.net.httpserver.HttpServer;
import example.extensions.HttpServerExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
// tag::user_guide[]
@ExtendWith(HttpServerExtension.class)
public class HttpServerDemo {
// end::user_guide[]
@SuppressWarnings("HttpUrlsUsage")
// tag::user_guide[]
@Test
void httpCall(HttpServer server) throws Exception {
String hostName = server.getAddress().getHostName();
int port = server.getAddress().getPort();
String rawUrl = "http://%s:%d/example".formatted(hostName, port);
URL requestUrl = URI.create(rawUrl).toURL();
String responseBody = sendRequest(requestUrl);
assertEquals("This is a test", responseBody);
}
private static String sendRequest(URL url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
int contentLength = connection.getContentLength();
try (InputStream response = url.openStream()) {
byte[] content = new byte[contentLength];
assertEquals(contentLength, response.read(content));
return new String(content, UTF_8);
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/IgnoredTestsDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.migrationsupport.EnableJUnit4MigrationSupport;
// @ExtendWith(IgnoreCondition.class)
@SuppressWarnings("removal")
@EnableJUnit4MigrationSupport
class IgnoredTestsDemo {
@Ignore
@Test
void testWillBeIgnored() {
}
@Test
void testWillBeExecuted() {
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/JUnit4Tests.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import org.junit.Test;
public class JUnit4Tests {
@Test
public void standardJUnit4Test() {
// perform assertions
}
}
================================================
FILE: documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import java.util.stream.Stream;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
class MethodSourceParameterResolutionDemo {
// @formatter:off
// tag::parameter_resolution_MethodSource_example[]
@RegisterExtension
static final IntegerResolver integerResolver = new IntegerResolver();
@ParameterizedTest
@MethodSource("factoryMethodWithArguments")
void testWithFactoryMethodWithArguments(String argument) {
assertTrue(argument.startsWith("2"));
}
static Stream factoryMethodWithArguments(int quantity) {
return Stream.of(
arguments(quantity + " apples"),
arguments(quantity + " lemons")
);
}
static class IntegerResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return parameterContext.getParameter().getType() == int.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return 2;
}
}
// end::parameter_resolution_MethodSource_example[]
// @formatter:on
}
================================================
FILE: documentation/src/test/java/example/MyFirstJUnitJupiterRecordTests.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import static org.junit.jupiter.api.Assertions.assertEquals;
import example.util.Calculator;
import org.junit.jupiter.api.Test;
record MyFirstJUnitJupiterRecordTests() {
@Test
void addition() {
assertEquals(2, new Calculator().add(1, 1));
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/MyFirstJUnitJupiterTests.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import static org.junit.jupiter.api.Assertions.assertEquals;
import example.util.Calculator;
import org.junit.jupiter.api.Test;
class MyFirstJUnitJupiterTests {
private final Calculator calculator = new Calculator();
@Test
void addition() {
assertEquals(2, calculator.add(1, 1));
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/MyRandomParametersTest.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import example.extensions.Random;
import org.junit.jupiter.api.Test;
// tag::user_guide[]
class MyRandomParametersTest {
MyRandomParametersTest(@Random int randomNumber) {
// Use randomNumber in constructor.
}
@Test
void injectsInteger(@Random int i, @Random int j) {
assertNotEquals(i, j);
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/OrderedNestedTestClassesDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestClassOrder;
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
class OrderedNestedTestClassesDemo {
@Nested
@Order(1)
class PrimaryTests {
@Test
void test1() {
}
}
@Nested
@Order(2)
class SecondaryTests {
@Test
void test2() {
}
}
}
//end::user_guide[]
================================================
FILE: documentation/src/test/java/example/OrderedTestsDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {
@Test
@Order(1)
void nullValues() {
// perform assertions against null values
}
@Test
@Order(2)
void emptyValues() {
// perform assertions against empty values
}
@Test
@Order(3)
void validValues() {
// perform assertions against valid values
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/ParameterizedClassDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD;
import java.time.Duration;
import java.util.Arrays;
import example.util.StringUtils;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.params.Parameter;
import org.junit.jupiter.params.ParameterizedClass;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
public class ParameterizedClassDemo {
@Nested
// tag::first_example[]
@ParameterizedClass
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
class PalindromeTests {
@Parameter
String candidate;
@Test
void palindrome() {
assertTrue(StringUtils.isPalindrome(candidate));
}
@Test
void reversePalindrome() {
String reverseCandidate = new StringBuilder(candidate).reverse().toString();
assertTrue(StringUtils.isPalindrome(reverseCandidate));
}
}
// end::first_example[]
@Nested
class ConstructorInjection {
@Nested
// tag::constructor_injection[]
@ParameterizedClass
@CsvSource({ "apple, 23", "banana, 42" })
class FruitTests {
final String fruit;
final int quantity;
FruitTests(String fruit, int quantity) {
this.fruit = fruit;
this.quantity = quantity;
}
@Test
void test() {
assertFruit(fruit);
assertQuantity(quantity);
}
@Test
void anotherTest() {
// ...
}
}
// end::constructor_injection[]
}
@Nested
class FieldInjection {
@Nested
// tag::field_injection[]
@ParameterizedClass
@CsvSource({ "apple, 23", "banana, 42" })
class FruitTests {
@Parameter(0)
String fruit;
@Parameter(1)
int quantity;
@Test
void test() {
assertFruit(fruit);
assertQuantity(quantity);
}
@Test
void anotherTest() {
// ...
}
}
// end::field_injection[]
}
@Nested
// tag::nested[]
@Execution(SAME_THREAD)
@ParameterizedClass
@ValueSource(strings = { "apple", "banana" })
class FruitTests {
@Parameter
String fruit;
@Nested
@ParameterizedClass
@ValueSource(ints = { 23, 42 })
class QuantityTests {
@Parameter
int quantity;
@ParameterizedTest
@ValueSource(strings = { "PT1H", "PT2H" })
void test(Duration duration) {
assertFruit(fruit);
assertQuantity(quantity);
assertFalse(duration.isNegative());
}
}
}
// end::nested[]
static void assertFruit(String fruit) {
assertTrue(Arrays.asList("apple", "banana", "cherry", "dewberry").contains(fruit),
() -> "not a fruit: " + fruit);
}
static void assertQuantity(int quantity) {
assertTrue(quantity > 0);
}
}
================================================
FILE: documentation/src/test/java/example/ParameterizedLifecycleDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.AfterParameterizedClassInvocation;
import org.junit.jupiter.params.BeforeParameterizedClassInvocation;
import org.junit.jupiter.params.Parameter;
import org.junit.jupiter.params.ParameterizedClass;
import org.junit.jupiter.params.provider.MethodSource;
public class ParameterizedLifecycleDemo {
@Nested
// tag::example[]
@ParameterizedClass
@MethodSource("textFiles")
class TextFileTests {
static List textFiles() {
return List.of(
// tag::custom_line_break[]
new TextFile("file1", "first content"),
// tag::custom_line_break[]
new TextFile("file2", "second content")
// tag::custom_line_break[]
);
}
@Parameter
TextFile textFile;
@BeforeParameterizedClassInvocation
static void beforeInvocation(TextFile textFile, @TempDir Path tempDir) throws Exception {
var filePath = tempDir.resolve(textFile.fileName); // <1>
textFile.path = Files.writeString(filePath, textFile.content);
}
//end::user_guide[]
@SuppressWarnings("DataFlowIssue")
//tag::user_guide[]
@AfterParameterizedClassInvocation
static void afterInvocation(TextFile textFile) throws Exception {
var actualContent = Files.readString(textFile.path); // <3>
assertEquals(textFile.content, actualContent, "Content must not have changed");
// Custom cleanup logic, if necessary
// File will be deleted automatically by @TempDir support
}
//end::user_guide[]
@SuppressWarnings("DataFlowIssue")
//tag::user_guide[]
@Test
void test() {
assertTrue(Files.exists(textFile.path)); // <2>
}
@Test
void anotherTest() {
// ...
}
static class TextFile {
final String fileName;
final String content;
// end::example[]
@Nullable
// tag::example[]
Path path;
TextFile(String fileName, String content) {
this.fileName = fileName;
this.content = content;
}
@Override
public String toString() {
return fileName;
}
}
}
// end::example[]
}
================================================
FILE: documentation/src/test/java/example/ParameterizedMigrationDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import java.util.Arrays;
import org.junit.jupiter.params.AfterParameterizedClassInvocation;
import org.junit.jupiter.params.BeforeParameterizedClassInvocation;
import org.junit.jupiter.params.ParameterizedClass;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
public class ParameterizedMigrationDemo {
// tag::before[]
@RunWith(Parameterized.class)
// end::before[]
static
// tag::before[]
public class JUnit4ParameterizedClassTests {
@Parameterized.Parameters
public static Iterable data() {
return Arrays.asList(new Object[][] { { 1, "foo" }, { 2, "bar" } });
}
// end::before[]
@SuppressWarnings("DefaultAnnotationParam")
// tag::before[]
@Parameterized.Parameter(0)
public int number;
@Parameterized.Parameter(1)
public String text;
@Parameterized.BeforeParam
public static void before(int number, String text) {
}
@Parameterized.AfterParam
public static void after() {
}
@org.junit.Test
public void someTest() {
}
@org.junit.Test
public void anotherTest() {
}
}
// end::before[]
// tag::after[]
@ParameterizedClass
@MethodSource("data")
// end::after[]
static
// tag::after[]
class JupiterParameterizedClassTests {
static Iterable data() {
return Arrays.asList(new Object[][] { { 1, "foo" }, { 2, "bar" } });
}
@org.junit.jupiter.params.Parameter(0)
int number;
@org.junit.jupiter.params.Parameter(1)
String text;
@BeforeParameterizedClassInvocation
static void before(int number, String text) {
}
@AfterParameterizedClassInvocation
static void after() {
}
@org.junit.jupiter.api.Test
void someTest() {
}
@org.junit.jupiter.api.Test
void anotherTest() {
}
}
// end::after[]
}
================================================
FILE: documentation/src/test/java/example/ParameterizedRecordDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedClass;
import org.junit.jupiter.params.provider.CsvSource;
public class ParameterizedRecordDemo {
// tag::example[]
@ParameterizedClass
@CsvSource({ "apple, 23", "banana, 42" })
record FruitTests(String fruit, int quantity) {
@Test
void test() {
assertFruit(fruit);
assertQuantity(quantity);
}
@Test
void anotherTest() {
// ...
}
}
// end::example[]
static void assertFruit(String fruit) {
assertTrue(Arrays.asList("apple", "banana", "cherry", "dewberry").contains(fruit));
}
static void assertQuantity(int quantity) {
assertTrue(quantity >= 0);
}
}
================================================
FILE: documentation/src/test/java/example/ParameterizedTestDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Named.named;
import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD;
import static org.junit.jupiter.params.provider.Arguments.argumentSet;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE;
import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL;
import java.io.File;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import example.domain.Person;
import example.domain.Person.Gender;
import example.util.StringUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestReporter;
import org.junit.jupiter.api.extension.AnnotatedElementContext;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.params.ArgumentCountValidationMode;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.aggregator.AggregateWith;
import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
import org.junit.jupiter.params.aggregator.SimpleArgumentsAggregator;
import org.junit.jupiter.params.converter.ConvertWith;
import org.junit.jupiter.params.converter.JavaTimeConversionPattern;
import org.junit.jupiter.params.converter.SimpleArgumentConverter;
import org.junit.jupiter.params.converter.TypedArgumentConverter;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.jupiter.params.provider.CsvFileSource;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.EmptySource;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.FieldSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.support.ParameterDeclarations;
@Execution(SAME_THREAD)
class ParameterizedTestDemo {
@BeforeEach
void printDisplayName(TestInfo testInfo) {
System.out.println(testInfo.getDisplayName());
}
// tag::first_example[]
@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
assertTrue(StringUtils.isPalindrome(candidate));
}
// end::first_example[]
// tag::ValueSource_example[]
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
assertTrue(argument > 0 && argument < 4);
}
// end::ValueSource_example[]
@Nested
class NullAndEmptySource_1 {
// tag::NullAndEmptySource_example1[]
@ParameterizedTest
@NullSource
@EmptySource
@ValueSource(strings = { " ", " ", "\t", "\n" })
void nullEmptyAndBlankStrings(String text) {
assertTrue(text == null || text.isBlank());
}
// end::NullAndEmptySource_example1[]
}
@Nested
class NullAndEmptySource_2 {
// tag::NullAndEmptySource_example2[]
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = { " ", " ", "\t", "\n" })
void nullEmptyAndBlankStrings(String text) {
assertTrue(text == null || text.isBlank());
}
// end::NullAndEmptySource_example2[]
}
// tag::EnumSource_example[]
@ParameterizedTest
@EnumSource(ChronoUnit.class)
void testWithEnumSource(TemporalUnit unit) {
assertNotNull(unit);
}
// end::EnumSource_example[]
// tag::EnumSource_example_autodetection[]
@ParameterizedTest
@EnumSource
void testWithEnumSourceWithAutoDetection(ChronoUnit unit) {
assertNotNull(unit);
}
// end::EnumSource_example_autodetection[]
// tag::EnumSource_include_example[]
@ParameterizedTest
@EnumSource(names = { "DAYS", "HOURS" })
void testWithEnumSourceInclude(ChronoUnit unit) {
assertTrue(EnumSet.of(ChronoUnit.DAYS, ChronoUnit.HOURS).contains(unit));
}
// end::EnumSource_include_example[]
// tag::EnumSource_range_example[]
@ParameterizedTest
@EnumSource(from = "HOURS", to = "DAYS")
void testWithEnumSourceRange(ChronoUnit unit) {
assertTrue(EnumSet.of(ChronoUnit.HOURS, ChronoUnit.HALF_DAYS, ChronoUnit.DAYS).contains(unit));
}
// end::EnumSource_range_example[]
// tag::EnumSource_exclude_example[]
@ParameterizedTest
@EnumSource(mode = EXCLUDE, names = { "ERAS", "FOREVER" })
void testWithEnumSourceExclude(ChronoUnit unit) {
assertFalse(EnumSet.of(ChronoUnit.ERAS, ChronoUnit.FOREVER).contains(unit));
}
// end::EnumSource_exclude_example[]
// tag::EnumSource_regex_example[]
@ParameterizedTest
@EnumSource(mode = MATCH_ALL, names = "^.*DAYS$")
void testWithEnumSourceRegex(ChronoUnit unit) {
assertTrue(unit.name().endsWith("DAYS"));
}
// end::EnumSource_regex_example[]
// tag::EnumSource_range_exclude_example[]
@ParameterizedTest
@EnumSource(from = "HOURS", to = "DAYS", mode = EXCLUDE, names = { "HALF_DAYS" })
void testWithEnumSourceRangeExclude(ChronoUnit unit) {
assertTrue(EnumSet.of(ChronoUnit.HOURS, ChronoUnit.DAYS).contains(unit));
assertFalse(EnumSet.of(ChronoUnit.HALF_DAYS).contains(unit));
}
// end::EnumSource_range_exclude_example[]
// tag::simple_MethodSource_example[]
@ParameterizedTest
@MethodSource("stringProvider")
void testWithExplicitLocalMethodSource(String argument) {
assertNotNull(argument);
}
static Stream stringProvider() {
return Stream.of("apple", "banana");
}
// end::simple_MethodSource_example[]
// tag::simple_MethodSource_without_value_example[]
@ParameterizedTest
@MethodSource
void testWithDefaultLocalMethodSource(String argument) {
assertNotNull(argument);
}
static Stream testWithDefaultLocalMethodSource() {
return Stream.of("apple", "banana");
}
// end::simple_MethodSource_without_value_example[]
// tag::primitive_MethodSource_example[]
@ParameterizedTest
@MethodSource("range")
void testWithRangeMethodSource(int argument) {
assertNotEquals(9, argument);
}
static IntStream range() {
return IntStream.range(0, 20).skip(10);
}
// end::primitive_MethodSource_example[]
// @formatter:off
// tag::multi_arg_MethodSource_example[]
@ParameterizedTest
@MethodSource("stringIntAndListProvider")
void testWithMultiArgMethodSource(String str, int num, List list) {
assertEquals(5, str.length());
assertTrue(num >=1 && num <=2);
assertEquals(2, list.size());
}
static Stream stringIntAndListProvider() {
return Stream.of(
arguments("apple", 1, Arrays.asList("a", "b")),
arguments("lemon", 2, Arrays.asList("x", "y"))
);
}
// end::multi_arg_MethodSource_example[]
// @formatter:on
// @formatter:off
// tag::default_field_FieldSource_example[]
@ParameterizedTest
@FieldSource
void arrayOfFruits(String fruit) {
assertFruit(fruit);
}
static final String[] arrayOfFruits = { "apple", "banana" };
// end::default_field_FieldSource_example[]
// @formatter:on
// @formatter:off
// tag::explicit_field_FieldSource_example[]
@ParameterizedTest
@FieldSource("listOfFruits")
void singleFieldSource(String fruit) {
assertFruit(fruit);
}
static final List listOfFruits = Arrays.asList("apple", "banana");
// end::explicit_field_FieldSource_example[]
// @formatter:on
// @formatter:off
// tag::multiple_fields_FieldSource_example[]
@ParameterizedTest
@FieldSource({ "listOfFruits", "additionalFruits" })
void multipleFieldSources(String fruit) {
assertFruit(fruit);
}
static final Collection additionalFruits = Arrays.asList("cherry", "dewberry");
// end::multiple_fields_FieldSource_example[]
// @formatter:on
// @formatter:off
// tag::named_arguments_FieldSource_example[]
@ParameterizedTest
@FieldSource
void namedArgumentsSupplier(String fruit) {
assertFruit(fruit);
}
static final Supplier> namedArgumentsSupplier = () -> Stream.of(
arguments(named("Apple", "apple")),
arguments(named("Banana", "banana"))
);
// end::named_arguments_FieldSource_example[]
// @formatter:on
private static void assertFruit(String fruit) {
assertTrue(Arrays.asList("apple", "banana", "cherry", "dewberry").contains(fruit));
}
// @formatter:off
// tag::multi_arg_FieldSource_example[]
@ParameterizedTest
@FieldSource("stringIntAndListArguments")
void testWithMultiArgFieldSource(String str, int num, List list) {
assertEquals(5, str.length());
assertTrue(num >=1 && num <=2);
assertEquals(2, list.size());
}
static List stringIntAndListArguments = Arrays.asList(
arguments("apple", 1, Arrays.asList("a", "b")),
arguments("lemon", 2, Arrays.asList("x", "y"))
);
// end::multi_arg_FieldSource_example[]
// @formatter:on
// @formatter:off
// tag::CsvSource_example[]
@ParameterizedTest
@CsvSource({
"apple, 1",
"banana, 2",
"'lemon, lime', 0xF1",
"strawberry, 700_000"
})
void testWithCsvSource(String fruit, int rank) {
assertNotNull(fruit);
assertNotEquals(0, rank);
}
// end::CsvSource_example[]
// @formatter:on
// tag::CsvFileSource_example[]
@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSourceFromClasspath(String country, int reference) {
assertNotNull(country);
assertNotEquals(0, reference);
}
@ParameterizedTest
@CsvFileSource(files = "src/test/resources/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSourceFromFile(String country, int reference) {
assertNotNull(country);
assertNotEquals(0, reference);
}
@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv", useHeadersInDisplayName = true)
void testWithCsvFileSourceAndHeaders(String country, int reference) {
assertNotNull(country);
assertNotEquals(0, reference);
}
// end::CsvFileSource_example[]
// tag::ArgumentsSource_example[]
@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void testWithArgumentsSource(String argument) {
assertNotNull(argument);
}
// end::ArgumentsSource_example[]
static
// tag::ArgumentsProvider_example[]
public class MyArgumentsProvider implements ArgumentsProvider {
@Override
public Stream extends Arguments> provideArguments(ParameterDeclarations parameters,
ExtensionContext context) {
return Stream.of("apple", "banana").map(Arguments::of);
}
}
// end::ArgumentsProvider_example[]
@ParameterizedTest
@ArgumentsSource(MyArgumentsProviderWithConstructorInjection.class)
void testWithArgumentsSourceWithConstructorInjection(String argument) {
assertNotNull(argument);
}
static
// tag::ArgumentsProviderWithConstructorInjection_example[]
public class MyArgumentsProviderWithConstructorInjection implements ArgumentsProvider {
private final TestInfo testInfo;
// end::ArgumentsProviderWithConstructorInjection_example[]
@SuppressWarnings("RedundantModifier")
// tag::ArgumentsProviderWithConstructorInjection_example[]
public MyArgumentsProviderWithConstructorInjection(TestInfo testInfo) {
this.testInfo = testInfo;
}
@Override
public Stream extends Arguments> provideArguments(ParameterDeclarations parameters,
ExtensionContext context) {
return Stream.of(Arguments.of(testInfo.getDisplayName()));
}
}
// end::ArgumentsProviderWithConstructorInjection_example[]
// tag::ParameterResolver_example[]
@BeforeEach
void beforeEach(TestInfo testInfo) {
// ...
}
@ParameterizedTest
@ValueSource(strings = "apple")
void testWithRegularParameterResolver(String argument, TestReporter testReporter) {
testReporter.publishEntry("argument", argument);
}
@AfterEach
void afterEach(TestInfo testInfo) {
// ...
}
// end::ParameterResolver_example[]
// tag::implicit_conversion_example[]
@ParameterizedTest
@ValueSource(strings = "SECONDS")
void testWithImplicitArgumentConversion(ChronoUnit argument) {
assertNotNull(argument.name());
}
// end::implicit_conversion_example[]
// tag::implicit_fallback_conversion_example[]
@ParameterizedTest
@ValueSource(strings = "42 Cats")
void testWithImplicitFallbackArgumentConversion(Book book) {
assertEquals("42 Cats", book.getTitle());
}
// end::implicit_fallback_conversion_example[]
static
// tag::implicit_fallback_conversion_example_Book[]
public class Book {
private final String title;
private Book(String title) {
this.title = title;
}
public static Book fromTitle(String title) {
return new Book(title);
}
public String getTitle() {
return this.title;
}
}
// end::implicit_fallback_conversion_example_Book[]
// @formatter:off
// tag::explicit_conversion_example[]
@ParameterizedTest
@EnumSource(ChronoUnit.class)
void testWithExplicitArgumentConversion(
@ConvertWith(ToStringArgumentConverter.class) String argument) {
assertNotNull(ChronoUnit.valueOf(argument));
}
// end::explicit_conversion_example[]
static
@SuppressWarnings({ "NullableProblems", "NullAway" })
// tag::explicit_conversion_example_ToStringArgumentConverter[]
public class ToStringArgumentConverter extends SimpleArgumentConverter {
@Override
protected Object convert(Object source, Class> targetType) {
assertEquals(String.class, targetType, "Can only convert to String");
if (source instanceof Enum> constant) {
return constant.name();
}
return String.valueOf(source);
}
}
// end::explicit_conversion_example_ToStringArgumentConverter[]
static
@SuppressWarnings({ "NullableProblems", "NullAway", "ConstantValue" })
// tag::explicit_conversion_example_TypedArgumentConverter[]
public class ToLengthArgumentConverter extends TypedArgumentConverter {
protected ToLengthArgumentConverter() {
super(String.class, Integer.class);
}
@Override
protected Integer convert(String source) {
return (source != null ? source.length() : 0);
}
}
// end::explicit_conversion_example_TypedArgumentConverter[]
// tag::explicit_java_time_converter[]
@ParameterizedTest
@ValueSource(strings = { "01.01.2017", "31.12.2017" })
void testWithExplicitJavaTimeConverter(
@JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) {
assertEquals(2017, argument.getYear());
}
// end::explicit_java_time_converter[]
// @formatter:on
// @formatter:off
// tag::ArgumentsAccessor_example[]
@ParameterizedTest
@CsvSource({
"Jane, Doe, F, 1990-05-20",
"John, Doe, M, 1990-10-22"
})
void testWithArgumentsAccessor(ArgumentsAccessor arguments) {
Person person = new Person(
arguments.getString(0),
arguments.getString(1),
arguments.get(2, Gender.class),
arguments.get(3, LocalDate.class));
if (person.getFirstName().equals("Jane")) {
assertEquals(Gender.F, person.getGender());
}
else {
assertEquals(Gender.M, person.getGender());
}
assertEquals("Doe", person.getLastName());
assertEquals(1990, person.getDateOfBirth().getYear());
}
// end::ArgumentsAccessor_example[]
// @formatter:on
// @formatter:off
// tag::ArgumentsAggregator_example[]
@ParameterizedTest
@CsvSource({
"Jane, Doe, F, 1990-05-20",
"John, Doe, M, 1990-10-22"
})
void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) {
// perform assertions against person
}
// end::ArgumentsAggregator_example[]
static
// tag::ArgumentsAggregator_example_PersonAggregator[]
public class PersonAggregator extends SimpleArgumentsAggregator {
@Override
protected Person aggregateArguments(ArgumentsAccessor arguments, Class> targetType,
AnnotatedElementContext context, int parameterIndex) {
return new Person(
arguments.getString(0),
arguments.getString(1),
arguments.get(2, Gender.class),
arguments.get(3, LocalDate.class));
}
}
// end::ArgumentsAggregator_example_PersonAggregator[]
// @formatter:on
// @formatter:off
// tag::ArgumentsAggregator_with_custom_annotation_example[]
@ParameterizedTest
@CsvSource({
"Jane, Doe, F, 1990-05-20",
"John, Doe, M, 1990-10-22"
})
void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) {
// perform assertions against person
}
// end::ArgumentsAggregator_with_custom_annotation_example[]
// tag::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[]
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AggregateWith(PersonAggregator.class)
public @interface CsvToPerson {
}
// end::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[]
// @formatter:on
// tag::custom_display_names[]
@DisplayName("Display name of container")
@ParameterizedTest(name = "{index} ==> the rank of {0} is {1}")
@CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 3" })
void testWithCustomDisplayNames(String fruit, int rank) {
}
// end::custom_display_names[]
// @formatter:off
// tag::named_arguments[]
@DisplayName("A parameterized test with named arguments")
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("namedArguments")
void testWithNamedArguments(File file) {
}
static Stream namedArguments() {
return Stream.of(
arguments(named("An important file", new File("path1"))),
arguments(named("Another file", new File("path2")))
);
}
// end::named_arguments[]
// @formatter:on
// @formatter:off
// tag::named_argument_set[]
@DisplayName("A parameterized test with named argument sets")
@ParameterizedTest
@FieldSource("argumentSets")
void testWithArgumentSets(File file1, File file2) {
}
static List argumentSets = Arrays.asList(
argumentSet("Important files", new File("path1"), new File("path2")),
argumentSet("Other files", new File("path3"), new File("path4"))
);
// end::named_argument_set[]
// @formatter:on
// tag::repeatable_annotations[]
@DisplayName("A parameterized test that makes use of repeatable annotations")
@ParameterizedTest
@MethodSource("someProvider")
@MethodSource("otherProvider")
void testWithRepeatedAnnotation(String argument) {
assertNotNull(argument);
}
static Stream someProvider() {
return Stream.of("foo");
}
static Stream otherProvider() {
return Stream.of("bar");
}
// end::repeatable_annotations[]
@Disabled("Fails prior to invoking the test method")
// tag::argument_count_validation[]
@ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.STRICT)
@CsvSource({ "42, -666" })
void testWithArgumentCountValidation(int number) {
assertTrue(number > 0);
}
// end::argument_count_validation[]
}
================================================
FILE: documentation/src/test/java/example/PollingTimeoutDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
class PollingTimeoutDemo {
// tag::user_guide[]
@Test
@Timeout(5) // Poll at most 5 seconds
void pollUntil() throws InterruptedException {
while (asynchronousResultNotAvailable()) {
Thread.sleep(250); // custom poll interval
}
// Obtain the asynchronous result and perform assertions
}
// end::user_guide[]
private boolean asynchronousResultNotAvailable() {
return false;
}
}
================================================
FILE: documentation/src/test/java/example/RepeatedTestsDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.logging.Logger;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.TestInfo;
// end::user_guide[]
// Use fully qualified names to avoid having them show up in the imports.
@org.junit.jupiter.api.parallel.Execution(org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD)
// tag::user_guide[]
class RepeatedTestsDemo {
private Logger logger = // ...
// end::user_guide[]
Logger.getLogger(RepeatedTestsDemo.class.getName());
// tag::user_guide[]
@BeforeEach
void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) {
int currentRepetition = repetitionInfo.getCurrentRepetition();
int totalRepetitions = repetitionInfo.getTotalRepetitions();
String methodName = testInfo.getTestMethod().get().getName();
logger.info("About to execute repetition %d of %d for %s".formatted( //
currentRepetition, totalRepetitions, methodName));
}
@RepeatedTest(10)
void repeatedTest() {
// ...
}
@RepeatedTest(5)
void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) {
assertEquals(5, repetitionInfo.getTotalRepetitions());
}
// end::user_guide[]
// Use fully qualified name to avoid having it show up in the imports.
@org.junit.jupiter.api.Disabled("intentional failures would break the build")
// tag::user_guide[]
@RepeatedTest(value = 8, failureThreshold = 2)
void repeatedTestWithFailureThreshold(RepetitionInfo repetitionInfo) {
// Simulate unexpected failure every second repetition
if (repetitionInfo.getCurrentRepetition() % 2 == 0) {
fail("Boom!");
}
}
@RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}")
@DisplayName("Repeat!")
void customDisplayName(TestInfo testInfo) {
assertEquals("Repeat! 1/1", testInfo.getDisplayName());
}
@RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME)
@DisplayName("Details...")
void customDisplayNameWithLongPattern(TestInfo testInfo) {
assertEquals("Details... :: repetition 1 of 1", testInfo.getDisplayName());
}
@RepeatedTest(value = 5, name = "Wiederholung {currentRepetition} von {totalRepetitions}")
void repeatedTestInGerman() {
// ...
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/SecondCustomEngine.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static java.net.InetAddress.getLoopbackAddress;
import static org.junit.platform.engine.TestExecutionResult.successful;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import org.jspecify.annotations.Nullable;
import org.junit.platform.engine.EngineDiscoveryRequest;
import org.junit.platform.engine.ExecutionRequest;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestEngine;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.descriptor.EngineDescriptor;
import org.junit.platform.engine.support.store.Namespace;
import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
//tag::user_guide[]
/**
* Second custom test engine implementation.
*/
public class SecondCustomEngine implements TestEngine {
//end::user_guide[]
@Nullable
//tag::user_guide[]
public ServerSocket socket;
@Override
public String getId() {
return "second-custom-test-engine";
}
//end::user_guide[]
@Nullable
//tag::user_guide[]
public ServerSocket getSocket() {
return this.socket;
}
@Override
public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) {
return new EngineDescriptor(uniqueId, "Second Custom Test Engine");
}
@Override
public void execute(ExecutionRequest request) {
request.getEngineExecutionListener()
// tag::custom_line_break[]
.executionStarted(request.getRootTestDescriptor());
NamespacedHierarchicalStore store = request.getStore();
socket = store.computeIfAbsent(Namespace.GLOBAL, "serverSocket", key -> {
try {
return new ServerSocket(0, 50, getLoopbackAddress());
}
catch (IOException e) {
throw new UncheckedIOException("Failed to start ServerSocket", e);
}
}, ServerSocket.class);
request.getEngineExecutionListener()
// tag::custom_line_break[]
.executionFinished(request.getRootTestDescriptor(), successful());
}
}
//end::user_guide[]
================================================
FILE: documentation/src/test/java/example/SlowTests.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD;
import java.util.stream.IntStream;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
@Tag("exclude")
@Disabled
class SlowTests {
@Execution(SAME_THREAD)
@Test
void a() {
foo();
}
@Test
void b() {
foo();
}
@Test
void c() {
foo();
}
@Test
void d() {
foo();
}
@Test
void e() {
foo();
}
@Test
void f() {
foo();
}
@Test
void g() {
foo();
}
@Test
void h() {
foo();
}
@Test
void i() {
foo();
}
@Test
void j() {
foo();
}
@Test
void k() {
foo();
}
@Test
void l() {
foo();
}
@Test
void m() {
foo();
}
@Test
void n() {
foo();
}
@Test
void o() {
foo();
}
@Test
void p() {
foo();
}
@Execution(SAME_THREAD)
@Test
void q() {
foo();
}
@Test
void r() {
foo();
}
@Test
void s() {
foo();
}
private void foo() {
IntStream.range(1, 100_000_000).mapToDouble(i -> Math.pow(i, i)).map(Math::sqrt).max();
}
}
================================================
FILE: documentation/src/test/java/example/StandardTests.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class StandardTests {
@BeforeAll
static void initAll() {
}
@BeforeEach
void init() {
}
@Test
void succeedingTest() {
}
// end::user_guide[]
@extensions.ExpectToFail
// tag::user_guide[]
@Test
void failingTest() {
fail("a failing test");
}
@Test
@Disabled("for demonstration purposes")
void skippedTest() {
// not executed
}
@Test
void abortedTest() {
assumeTrue("abc".contains("Z"));
fail("test should have been aborted");
}
@AfterEach
void tearDown() {
}
@AfterAll
static void tearDownAll() {
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/SuiteDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
//tag::user_guide[]
import org.junit.platform.suite.api.IncludeClassNamePatterns;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
import org.junit.platform.suite.api.SuiteDisplayName;
@Suite
@SuiteDisplayName("JUnit Platform Suite Demo")
@SelectPackages("example")
@IncludeClassNamePatterns(".*Tests")
//end::user_guide[]
@org.junit.platform.suite.api.ExcludeTags("exclude")
//tag::user_guide[]
class SuiteDemo {
}
//end::user_guide[]
================================================
FILE: documentation/src/test/java/example/SystemPropertyExtensionDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestClassOrder;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.util.ClearSystemProperty;
import org.junit.jupiter.api.util.ReadsSystemProperty;
import org.junit.jupiter.api.util.RestoreSystemProperties;
import org.junit.jupiter.api.util.SetSystemProperty;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class SystemPropertyExtensionDemo {
// tag::systemproperty_clear_simple[]
@Test
@ClearSystemProperty(key = "some property")
void testClearingProperty() {
assertThat(System.getProperty("some property")).isNull();
}
// end::systemproperty_clear_simple[]
// tag::systemproperty_set_simple[]
@Test
@SetSystemProperty(key = "some property", value = "new value")
void testSettingProperty() {
assertThat(System.getProperty("some property")).isEqualTo("new value");
}
// end::systemproperty_set_simple[]
// tag::systemproperty_using_set_and_clear[]
@Test
@ClearSystemProperty(key = "1st property")
@ClearSystemProperty(key = "2nd property")
@SetSystemProperty(key = "3rd property", value = "new value")
void testClearingAndSettingProperty() {
assertThat(System.getProperty("1st property")).isNull();
assertThat(System.getProperty("2nd property")).isNull();
assertThat(System.getProperty("3rd property")).isEqualTo("new value");
}
// end::systemproperty_using_set_and_clear[]
@Nested
// tag::systemproperty_using_at_class_level[]
@ClearSystemProperty(key = "some property")
class MySystemPropertyTest {
@Test
@SetSystemProperty(key = "some property", value = "new value")
void clearedAtClasslevel() {
assertThat(System.getProperty("some property")).isEqualTo("new value");
}
}
// end::systemproperty_using_at_class_level[]
// tag::systemproperty_restore_test[]
@ParameterizedTest
@ValueSource(strings = { "foo", "bar" })
@RestoreSystemProperties
void parameterizedTest(String value) {
System.setProperty("some parameterized property", value);
System.setProperty("some other dynamic property", "my code calculates somehow");
}
// end::systemproperty_restore_test[]
@Nested
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
class SystemPropertyRestoreExample {
@Nested
@Order(1)
// tag::systemproperty_class_restore_setup[]
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@RestoreSystemProperties
class MySystemPropertyRestoreTest {
@BeforeAll
void beforeAll() {
System.setProperty("A", "A value");
}
@BeforeEach
void beforeEach() {
System.setProperty("B", "B value");
}
@Test
void isolatedTest1() {
System.setProperty("C", "C value");
}
@Test
void isolatedTest2() {
assertThat(System.getProperty("A")).isEqualTo("A value");
assertThat(System.getProperty("B")).isEqualTo("B value");
// Class-level @RestoreSystemProperties restores "C" to original state
assertThat(System.getProperty("C")).isNull();
}
}
// end::systemproperty_class_restore_setup[]
@Nested
@Order(2)
// tag::systemproperty_class_restore_isolated_class[]
@ReadsSystemProperty
class SomeOtherTestClass {
@Test
void isolatedTest() {
assertThat(System.getProperty("A")).isNull();
assertThat(System.getProperty("B")).isNull();
assertThat(System.getProperty("C")).isNull();
}
}
// end::systemproperty_class_restore_isolated_class[]
}
// tag::systemproperty_method_combine_all_test[]
@ParameterizedTest
@ValueSource(ints = { 100, 500, 1000 })
@RestoreSystemProperties
@SetSystemProperty(key = "DISABLE_CACHE", value = "TRUE")
@ClearSystemProperty(key = "COPYWRITE_OVERLAY_TEXT")
void imageGenerationTest(int imageSize) {
System.setProperty("IMAGE_SIZE", String.valueOf(imageSize)); // Requires restore
// Test your image generation utility with the current system properties
}
// end::systemproperty_method_combine_all_test[]
}
================================================
FILE: documentation/src/test/java/example/TaggingDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("fast")
@Tag("model")
class TaggingDemo {
@Test
@Tag("taxes")
void testingTaxCalculation() {
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/TempDirectoryDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import example.TempDirectoryDemo.InMemoryTempDirDemo.JimfsTempDirFactory;
import example.util.ListWriter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.AnnotatedElementContext;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.io.TempDirDeletionStrategy;
import org.junit.jupiter.api.io.TempDirFactory;
@SuppressWarnings("NewClassNamingConvention")
class TempDirectoryDemo {
// tag::user_guide_parameter_injection[]
@Test
void writeItemsToFile(@TempDir Path tempDir) throws IOException {
Path file = tempDir.resolve("test.txt");
new ListWriter(file).write("a", "b", "c");
assertEquals(List.of("a,b,c"), Files.readAllLines(file));
}
// end::user_guide_parameter_injection[]
// tag::user_guide_multiple_directories[]
@Test
void copyFileFromSourceToTarget(@TempDir Path source, @TempDir Path target) throws IOException {
Path sourceFile = source.resolve("test.txt");
new ListWriter(sourceFile).write("a", "b", "c");
Path targetFile = Files.copy(sourceFile, target.resolve("test.txt"));
assertNotEquals(sourceFile, targetFile);
assertEquals(List.of("a,b,c"), Files.readAllLines(targetFile));
}
// end::user_guide_multiple_directories[]
static
// tag::user_guide_field_injection[]
class SharedTempDirectoryDemo {
@TempDir
static Path sharedTempDir;
@Test
void writeItemsToFile() throws IOException {
Path file = sharedTempDir.resolve("test.txt");
new ListWriter(file).write("a", "b", "c");
assertEquals(List.of("a,b,c"), Files.readAllLines(file));
}
@Test
void anotherTestThatUsesTheSameTempDir() {
// use sharedTempDir
}
}
// end::user_guide_field_injection[]
static
// tag::user_guide_cleanup_mode[]
class CleanupModeDemo {
@Test
void fileTest(@TempDir(cleanup = ON_SUCCESS) Path tempDir) {
// perform test
}
}
// end::user_guide_cleanup_mode[]
static
// tag::user_guide_factory_name_prefix[]
class TempDirFactoryDemo {
@Test
void factoryTest(@TempDir(factory = Factory.class) Path tempDir) {
assertTrue(tempDir.getFileName().toString().startsWith("factoryTest"));
}
static class Factory implements TempDirFactory {
@Override
public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext)
throws IOException {
return Files.createTempDirectory(extensionContext.getRequiredTestMethod().getName());
}
}
}
// end::user_guide_factory_name_prefix[]
static
// tag::user_guide_factory_jimfs[]
class InMemoryTempDirDemo {
@Test
void test(@TempDir(factory = JimfsTempDirFactory.class) Path tempDir) {
// perform test
}
static class JimfsTempDirFactory implements TempDirFactory {
private final FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());
@Override
public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext)
throws IOException {
return Files.createTempDirectory(fileSystem.getPath("/"), "junit-");
}
@Override
public void close() throws IOException {
fileSystem.close();
}
}
}
// end::user_guide_factory_jimfs[]
static
// tag::user_guide_deletion_strategy[]
class DeletionStrategyDemo {
@Test
void test(@TempDir(deletionStrategy = TempDirDeletionStrategy.IgnoreFailures.class) Path tempDir) {
// perform test
}
}
// end::user_guide_deletion_strategy[]
// tag::user_guide_composed_annotation[]
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@TempDir(factory = JimfsTempDirFactory.class)
@interface JimfsTempDir {
}
// end::user_guide_composed_annotation[]
static
// tag::user_guide_composed_annotation_usage[]
class JimfsTempDirAnnotationDemo {
@Test
void test(@JimfsTempDir Path tempDir) {
// perform test
}
}
// end::user_guide_composed_annotation_usage[]
}
================================================
FILE: documentation/src/test/java/example/TestInfoDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
@DisplayName("TestInfo Demo")
class TestInfoDemo {
@BeforeAll
static void beforeAll(TestInfo testInfo) {
assertEquals("TestInfo Demo", testInfo.getDisplayName());
}
TestInfoDemo(TestInfo testInfo) {
String displayName = testInfo.getDisplayName();
assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()"));
}
@BeforeEach
void init(TestInfo testInfo) {
String displayName = testInfo.getDisplayName();
assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()"));
}
@Test
@DisplayName("TEST 1")
@Tag("my-tag")
void test1(TestInfo testInfo) {
assertEquals("TEST 1", testInfo.getDisplayName());
assertTrue(testInfo.getTags().contains("my-tag"));
}
@Test
void test2() {
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/TestReporterDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.MediaType;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestReporter;
import org.junit.jupiter.api.io.TempDir;
// tag::user_guide[]
class TestReporterDemo {
@Test
void reportSingleValue(TestReporter testReporter) {
testReporter.publishEntry("a status message");
}
@Test
void reportKeyValuePair(TestReporter testReporter) {
testReporter.publishEntry("a key", "a value");
}
@Test
void reportMultipleKeyValuePairs(TestReporter testReporter) {
Map values = new HashMap<>();
values.put("user name", "dk38");
values.put("award year", "1974");
testReporter.publishEntry(values);
}
@Test
void reportFiles(TestReporter testReporter, @TempDir Path tempDir) throws Exception {
testReporter.publishFile("test1.txt", MediaType.TEXT_PLAIN_UTF_8, file -> Files.write(file, List.of("Test 1")));
Path existingFile = Files.write(tempDir.resolve("test2.txt"), List.of("Test 2"));
testReporter.publishFile(existingFile, MediaType.TEXT_PLAIN_UTF_8);
testReporter.publishDirectory("test3", dir -> {
Files.write(dir.resolve("nested1.txt"), List.of("Nested content 1"));
Files.write(dir.resolve("nested2.txt"), List.of("Nested content 2"));
});
Path existingDir = Files.createDirectory(tempDir.resolve("test4"));
Files.write(existingDir.resolve("nested1.txt"), List.of("Nested content 1"));
Files.write(existingDir.resolve("nested2.txt"), List.of("Nested content 2"));
testReporter.publishDirectory(existingDir);
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/TestTemplateDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
class TestTemplateDemo {
// tag::user_guide[]
final List fruits = Arrays.asList("apple", "banana", "lemon");
@TestTemplate
@ExtendWith(MyTestTemplateInvocationContextProvider.class)
void testTemplate(String fruit) {
assertTrue(fruits.contains(fruit));
}
// end::user_guide[]
static
// @formatter:off
// tag::user_guide[]
public class MyTestTemplateInvocationContextProvider
implements TestTemplateInvocationContextProvider {
@Override
public boolean supportsTestTemplate(ExtensionContext context) {
return true;
}
@Override
public Stream provideTestTemplateInvocationContexts(
ExtensionContext context) {
return Stream.of(invocationContext("apple"), invocationContext("banana"));
}
private TestTemplateInvocationContext invocationContext(String parameter) {
return new TestTemplateInvocationContext() {
@Override
public String getDisplayName(int invocationIndex) {
return parameter;
}
@Override
public List getAdditionalExtensions() {
return List.of(new ParameterResolver() {
@Override
public boolean supportsParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return parameterContext.getParameter().getType().equals(String.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return parameter;
}
});
}
};
}
}
// end::user_guide[]
// @formatter:on
}
================================================
FILE: documentation/src/test/java/example/TestingAStackDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::user_guide[]
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.EmptyStackException;
import java.util.Stack;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@DisplayName("A stack")
class TestingAStackDemo {
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
Stack stack;
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/TimeoutDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.Timeout.ThreadMode;
// tag::user_guide[]
@Tag("timeout")
class TimeoutDemo {
@BeforeEach
@Timeout(5)
void setUp() {
// fails if execution time exceeds 5 seconds
}
@Test
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
void failsIfExecutionTimeExceeds500Milliseconds() {
// fails if execution time exceeds 500 milliseconds
}
@Test
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS, threadMode = ThreadMode.SEPARATE_THREAD)
void failsIfExecutionTimeExceeds500MillisecondsInSeparateThread() {
// fails if execution time exceeds 500 milliseconds, the test code is executed in a separate thread
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/UsingTheLauncherDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
import static org.junit.platform.engine.TestExecutionResult.Status.FAILED;
import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;
import java.io.PrintWriter;
import java.nio.file.Path;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.platform.engine.CancellationToken;
import org.junit.platform.engine.FilterResult;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryListener;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.LauncherExecutionRequest;
import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.LauncherSessionListener;
import org.junit.platform.launcher.PostDiscoveryFilter;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestIdentifier;
import org.junit.platform.launcher.TestPlan;
import org.junit.platform.launcher.core.LauncherConfig;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherExecutionRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
import org.junit.platform.launcher.listeners.TestExecutionSummary;
import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener;
/**
* @since 5.0
*/
class UsingTheLauncherDemo {
@Tag("exclude")
@Test
@SuppressWarnings("unused")
void execution() {
// @formatter:off
// tag::execution[]
LauncherDiscoveryRequest discoveryRequest = LauncherDiscoveryRequestBuilder.request()
.selectors(
selectPackage("com.example.mytests"),
selectClass(MyTestClass.class)
)
.filters(
includeClassNamePatterns(".*Tests")
)
// end::execution[]
.configurationParameter("enableHttpServer", "false")
// tag::execution[]
.build();
SummaryGeneratingListener listener = new SummaryGeneratingListener();
try (LauncherSession session = LauncherFactory.openSession()) {
Launcher launcher = session.getLauncher();
// Register one ore more listeners of your choice.
launcher.registerTestExecutionListeners(listener);
// Discover tests and build a test plan.
TestPlan testPlan = launcher.discover(discoveryRequest);
// Execute the test plan.
launcher.execute(testPlan);
// Alternatively, execute the discovery request directly.
launcher.execute(discoveryRequest);
}
TestExecutionSummary summary = listener.getSummary();
// Do something with the summary...
// end::execution[]
// @formatter:on
}
@Test
void launcherConfig() {
Path reportsDir = Path.of("target", "xml-reports");
PrintWriter out = new PrintWriter(System.out);
// @formatter:off
// tag::launcherConfig[]
LauncherConfig launcherConfig = LauncherConfig.builder()
.enableTestEngineAutoRegistration(false)
.enableLauncherSessionListenerAutoRegistration(false)
.enableLauncherDiscoveryListenerAutoRegistration(false)
.enablePostDiscoveryFilterAutoRegistration(false)
.enableTestExecutionListenerAutoRegistration(false)
.addTestEngines(new CustomTestEngine())
.addLauncherSessionListeners(new CustomLauncherSessionListener())
.addLauncherDiscoveryListeners(new CustomLauncherDiscoveryListener())
.addPostDiscoveryFilters(new CustomPostDiscoveryFilter())
.addTestExecutionListeners(new LegacyXmlReportGeneratingListener(reportsDir, out))
.addTestExecutionListeners(new CustomTestExecutionListener())
.build();
LauncherDiscoveryRequest discoveryRequest = LauncherDiscoveryRequestBuilder.request()
.selectors(selectPackage("com.example.mytests"))
.build();
try (LauncherSession session = LauncherFactory.openSession(launcherConfig)) {
session.getLauncher().execute(discoveryRequest);
}
// end::launcherConfig[]
// @formatter:on
}
@Test
@SuppressWarnings("unused")
void cancellationDirect() {
// tag::cancellation-direct[]
CancellationToken cancellationToken = CancellationToken.create(); // <1>
TestExecutionListener failFastListener = new TestExecutionListener() {
@Override
public void executionFinished(TestIdentifier identifier, TestExecutionResult result) {
if (result.getStatus() == FAILED) {
cancellationToken.cancel(); // <2>
}
}
};
// end::cancellation-direct[]
// @formatter:off
// tag::cancellation-direct[]
LauncherExecutionRequest executionRequest = LauncherDiscoveryRequestBuilder.request()
.selectors(selectClass(MyTestClass.class))
.forExecution()
// end::cancellation-direct[]
// @formatter:on
// tag::cancellation-direct[]
.cancellationToken(cancellationToken) // <3>
.listeners(failFastListener) // <4>
.build();
try (LauncherSession session = LauncherFactory.openSession()) {
session.getLauncher().execute(executionRequest); // <5>
}
// end::cancellation-direct[]
}
@Test
@SuppressWarnings("unused")
void cancellationFromDiscoveryRequest() {
CancellationToken cancellationToken = CancellationToken.create();
TestExecutionListener failFastListener = new TestExecutionListener() {
@Override
public void executionFinished(TestIdentifier identifier, TestExecutionResult result) {
if (result.getStatus() == FAILED) {
cancellationToken.cancel();
}
}
};
// @formatter:off
// tag::cancellation-discovery-request[]
LauncherDiscoveryRequest discoveryRequest = LauncherDiscoveryRequestBuilder.request()
.selectors(selectClass(MyTestClass.class))
.build(); // <1>
// end::cancellation-discovery-request[]
// @formatter:on
// tag::cancellation-discovery-request[]
LauncherExecutionRequest executionRequest = LauncherExecutionRequestBuilder.request(discoveryRequest) // <2>
.cancellationToken(cancellationToken) // <3>
.listeners(failFastListener) // <4>
.build();
try (LauncherSession session = LauncherFactory.openSession()) {
session.getLauncher().execute(executionRequest); // <5>
}
// end::cancellation-discovery-request[]
}
@Test
@SuppressWarnings("unused")
void cancellationFromTestPlan() {
CancellationToken cancellationToken = CancellationToken.create();
TestExecutionListener failFastListener = new TestExecutionListener() {
@Override
public void executionFinished(TestIdentifier identifier, TestExecutionResult result) {
if (result.getStatus() == FAILED) {
cancellationToken.cancel();
}
}
};
// @formatter:off
// tag::cancellation-test-plan[]
LauncherDiscoveryRequest discoveryRequest = LauncherDiscoveryRequestBuilder.request()
.selectors(selectClass(MyTestClass.class))
.build(); // <1>
// end::cancellation-test-plan[]
// @formatter:on
// tag::cancellation-test-plan[]
try (LauncherSession session = LauncherFactory.openSession()) {
var launcher = session.getLauncher();
TestPlan testPlan = launcher.discover(discoveryRequest); // <2>
LauncherExecutionRequest executionRequest = LauncherExecutionRequestBuilder.request(testPlan) // <3>
.cancellationToken(cancellationToken) // <4>
.listeners(failFastListener) // <5>
.build();
launcher.execute(executionRequest); // <6>
}
// end::cancellation-test-plan[]
}
}
class MyTestClass {
}
class CustomTestExecutionListener implements TestExecutionListener {
}
class CustomLauncherSessionListener implements LauncherSessionListener {
}
class CustomLauncherDiscoveryListener implements LauncherDiscoveryListener {
}
class CustomPostDiscoveryFilter implements PostDiscoveryFilter {
@Override
public FilterResult apply(TestDescriptor object) {
return FilterResult.included("includes everything");
}
}
================================================
FILE: documentation/src/test/java/example/UsingTheLauncherForDiscoveryDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example;
// tag::imports[]
import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;
import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.discoveryRequest;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.TestPlan;
import org.junit.platform.launcher.core.LauncherFactory;
// end::imports[]
/**
* @since 6.0
*/
class UsingTheLauncherForDiscoveryDemo {
@org.junit.jupiter.api.Test
@SuppressWarnings("unused")
void discovery() {
// @formatter:off
// tag::discovery[]
LauncherDiscoveryRequest discoveryRequest = discoveryRequest()
.selectors(
selectPackage("com.example.mytests"),
selectClass(MyTestClass.class)
)
.filters(
includeClassNamePatterns(".*Tests")
)
.build();
try (LauncherSession session = LauncherFactory.openSession()) {
TestPlan testPlan = session.getLauncher().discover(discoveryRequest);
// ... discover additional test plans or execute tests
}
// end::discovery[]
// @formatter:on
}
static class MyTestClass {
}
}
================================================
FILE: documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.callbacks;
// tag::user_guide[]
import static example.callbacks.Logger.afterAllMethod;
import static example.callbacks.Logger.afterEachMethod;
import static example.callbacks.Logger.beforeAllMethod;
import static example.callbacks.Logger.beforeEachMethod;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
/**
* Abstract base class for tests that use the database.
*/
abstract class AbstractDatabaseTests {
@BeforeAll
static void createDatabase() {
beforeAllMethod(AbstractDatabaseTests.class.getSimpleName() + ".createDatabase()");
}
@BeforeEach
void connectToDatabase() {
beforeEachMethod(AbstractDatabaseTests.class.getSimpleName() + ".connectToDatabase()");
}
@AfterEach
void disconnectFromDatabase() {
afterEachMethod(AbstractDatabaseTests.class.getSimpleName() + ".disconnectFromDatabase()");
}
@AfterAll
static void destroyDatabase() {
afterAllMethod(AbstractDatabaseTests.class.getSimpleName() + ".destroyDatabase()");
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.callbacks;
// tag::user_guide[]
import static example.callbacks.Logger.afterEachMethod;
import static example.callbacks.Logger.beforeEachMethod;
import static example.callbacks.Logger.testMethod;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
/**
* Example of "broken" lifecycle method configuration.
*
* Test data is inserted before the database connection has been opened.
*
*
Database connection is closed before deleting test data.
*/
@ExtendWith({ Extension1.class, Extension2.class })
class BrokenLifecycleMethodConfigDemo {
@BeforeEach
void connectToDatabase() {
beforeEachMethod(getClass().getSimpleName() + ".connectToDatabase()");
}
@BeforeEach
void insertTestDataIntoDatabase() {
beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()");
}
@Test
void testDatabaseFunctionality() {
testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()");
}
@AfterEach
void deleteTestDataFromDatabase() {
afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()");
}
@AfterEach
void disconnectFromDatabase() {
afterEachMethod(getClass().getSimpleName() + ".disconnectFromDatabase()");
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.callbacks;
// tag::user_guide[]
import static example.callbacks.Logger.afterEachMethod;
import static example.callbacks.Logger.beforeAllMethod;
import static example.callbacks.Logger.beforeEachMethod;
import static example.callbacks.Logger.testMethod;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
/**
* Extension of {@link AbstractDatabaseTests} that inserts test data
* into the database (after the database connection has been opened)
* and deletes test data (before the database connection is closed).
*/
@ExtendWith({ Extension1.class, Extension2.class })
class DatabaseTestsDemo extends AbstractDatabaseTests {
@BeforeAll
static void beforeAll() {
beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".beforeAll()");
}
@BeforeEach
void insertTestDataIntoDatabase() {
beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()");
}
@Test
void testDatabaseFunctionality() {
testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()");
}
@AfterEach
void deleteTestDataFromDatabase() {
afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()");
}
@AfterAll
static void afterAll() {
beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".afterAll()");
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/callbacks/Extension1.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.callbacks;
// tag::user_guide[]
import static example.callbacks.Logger.afterEachCallback;
import static example.callbacks.Logger.beforeEachCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class Extension1 implements BeforeEachCallback, AfterEachCallback {
@Override
public void beforeEach(ExtensionContext context) {
beforeEachCallback(this);
}
@Override
public void afterEach(ExtensionContext context) {
afterEachCallback(this);
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/callbacks/Extension2.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.callbacks;
// tag::user_guide[]
import static example.callbacks.Logger.afterEachCallback;
import static example.callbacks.Logger.beforeEachCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class Extension2 implements BeforeEachCallback, AfterEachCallback {
@Override
public void beforeEach(ExtensionContext context) {
beforeEachCallback(this);
}
@Override
public void afterEach(ExtensionContext context) {
afterEachCallback(this);
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/callbacks/Logger.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.callbacks;
import java.util.function.Supplier;
import org.junit.jupiter.api.extension.Extension;
class Logger {
static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(Logger.class.getName());
static void beforeAllMethod(String text) {
log(() -> "@BeforeAll " + text);
}
static void beforeEachCallback(Extension extension) {
log(() -> " " + extension.getClass().getSimpleName() + ".beforeEach()");
}
static void beforeEachMethod(String text) {
log(() -> " @BeforeEach " + text);
}
static void testMethod(String text) {
log(() -> " @Test " + text);
}
static void afterEachMethod(String text) {
log(() -> " @AfterEach " + text);
}
static void afterEachCallback(Extension extension) {
log(() -> " " + extension.getClass().getSimpleName() + ".afterEach()");
}
static void afterAllMethod(String text) {
log(() -> "@AfterAll " + text);
}
private static void log(Supplier supplier) {
// System.err.println(supplier.get());
logger.info(supplier);
}
}
================================================
FILE: documentation/src/test/java/example/callbacks/package-info.java
================================================
@NullMarked
package example.callbacks;
import org.jspecify.annotations.NullMarked;
================================================
FILE: documentation/src/test/java/example/defaultmethods/ComparableContract.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.defaultmethods;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
// tag::user_guide[]
public interface ComparableContract> extends Testable {
T createSmallerValue();
@Test
default void returnsZeroWhenComparedToItself() {
T value = createValue();
assertEquals(0, value.compareTo(value));
}
@Test
default void returnsPositiveNumberWhenComparedToSmallerValue() {
T value = createValue();
T smallerValue = createSmallerValue();
assertTrue(value.compareTo(smallerValue) > 0);
}
@Test
default void returnsNegativeNumberWhenComparedToLargerValue() {
T value = createValue();
T smallerValue = createSmallerValue();
assertTrue(smallerValue.compareTo(value) < 0);
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/defaultmethods/EqualsContract.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.defaultmethods;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import org.junit.jupiter.api.Test;
// tag::user_guide[]
public interface EqualsContract extends Testable {
T createNotEqualValue();
@Test
default void valueEqualsItself() {
T value = createValue();
assertEquals(value, value);
}
@Test
default void valueDoesNotEqualNull() {
T value = createValue();
assertNotEquals(null, value);
}
@Test
default void valueDoesNotEqualDifferentValue() {
T value = createValue();
T differentValue = createNotEqualValue();
assertNotEquals(value, differentValue);
assertNotEquals(differentValue, value);
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/defaultmethods/StringTests.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.defaultmethods;
// tag::user_guide[]
class StringTests implements ComparableContract, EqualsContract {
@Override
public String createValue() {
return "banana";
}
@Override
public String createSmallerValue() {
return "apple"; // 'a' < 'b' in "banana"
}
@Override
public String createNotEqualValue() {
return "cherry";
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/defaultmethods/Testable.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.defaultmethods;
// tag::user_guide[]
public interface Testable {
T createValue();
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/defaultmethods/package-info.java
================================================
@NullMarked
package example.defaultmethods;
import org.jspecify.annotations.NullMarked;
================================================
FILE: documentation/src/test/java/example/exception/AssertDoesNotThrowExceptionDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.exception;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import org.junit.jupiter.api.Test;
class AssertDoesNotThrowExceptionDemo {
// tag::user_guide[]
@Test
void testExceptionIsNotThrown() {
assertDoesNotThrow(() -> {
shouldNotThrowException();
});
}
void shouldNotThrowException() {
}
// end::user_guide[]
}
================================================
FILE: documentation/src/test/java/example/exception/ExceptionAssertionDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.exception;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
class ExceptionAssertionDemo {
// @formatter:off
// tag::user_guide[]
@Test
void testExpectedExceptionIsThrown() {
// The following assertion succeeds because the code under assertion
// throws the expected IllegalArgumentException.
// The assertion also returns the thrown exception which can be used for
// further assertions like asserting the exception message.
IllegalArgumentException exception =
assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("expected message");
});
assertEquals("expected message", exception.getMessage());
// The following assertion also succeeds because the code under assertion
// throws IllegalArgumentException which is a subclass of RuntimeException.
assertThrows(RuntimeException.class, () -> {
throw new IllegalArgumentException("expected message");
});
}
// end::user_guide[]
// @formatter:on
}
================================================
FILE: documentation/src/test/java/example/exception/ExceptionAssertionExactDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.exception;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import extensions.ExpectToFail;
import org.junit.jupiter.api.Test;
public class ExceptionAssertionExactDemo {
@ExpectToFail
// @formatter:off
// tag::user_guide[]
@Test
void testExpectedExceptionIsThrown() {
// The following assertion succeeds because the code under assertion throws
// IllegalArgumentException which is exactly equal to the expected type.
// The assertion also returns the thrown exception which can be used for
// further assertions like asserting the exception message.
IllegalArgumentException exception =
assertThrowsExactly(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("expected message");
});
assertEquals("expected message", exception.getMessage());
// The following assertion fails because the assertion expects exactly
// RuntimeException to be thrown, not subclasses of RuntimeException.
assertThrowsExactly(RuntimeException.class, () -> {
throw new IllegalArgumentException("expected message");
});
}
// end::user_guide[]
// @formatter:on
}
================================================
FILE: documentation/src/test/java/example/exception/FailedAssertionDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.exception;
import static org.junit.jupiter.api.Assertions.assertEquals;
import example.util.Calculator;
import extensions.ExpectToFail;
import org.junit.jupiter.api.Test;
class FailedAssertionDemo {
// tag::user_guide[]
private final Calculator calculator = new Calculator();
// end::user_guide[]
@ExpectToFail
// tag::user_guide[]
@Test
void failsDueToUncaughtAssertionError() {
// The following incorrect assertion will cause a test failure.
// The expected value should be 2 instead of 99.
assertEquals(99, calculator.add(1, 1));
}
// end::user_guide[]
}
================================================
FILE: documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.exception;
import java.io.IOException;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
// @formatter:off
// tag::user_guide[]
public class IgnoreIOExceptionExtension implements TestExecutionExceptionHandler {
@Override
public void handleTestExecutionException(ExtensionContext context, Throwable throwable)
throws Throwable {
if (throwable instanceof IOException) {
return;
}
throw throwable;
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.exception;
import java.io.IOException;
import extensions.ExpectToFail;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(IgnoreIOExceptionExtension.class)
class IgnoreIOExceptionTests {
@Test
void shouldSucceed() throws IOException {
throw new IOException("any");
}
@Test
@ExpectToFail
void shouldFail() {
throw new RuntimeException("any");
}
}
================================================
FILE: documentation/src/test/java/example/exception/MultipleHandlersTestCase.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.exception;
import example.exception.MultipleHandlersTestCase.ThirdExecutedHandler;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
// @formatter:off
// tag::user_guide[]
// Register handlers for @Test, @BeforeEach, @AfterEach as well as @BeforeAll and @AfterAll
@ExtendWith(ThirdExecutedHandler.class)
class MultipleHandlersTestCase {
// Register handlers for @Test, @BeforeEach, @AfterEach only
@ExtendWith(SecondExecutedHandler.class)
@ExtendWith(FirstExecutedHandler.class)
@Test
void testMethod() {
}
// end::user_guide[]
static class FirstExecutedHandler implements TestExecutionExceptionHandler {
@Override
public void handleTestExecutionException(ExtensionContext context, Throwable ex)
throws Throwable {
throw ex;
}
}
static class SecondExecutedHandler implements LifecycleMethodExecutionExceptionHandler {
@Override
public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex)
throws Throwable {
throw ex;
}
}
static class ThirdExecutedHandler implements LifecycleMethodExecutionExceptionHandler {
@Override
public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex)
throws Throwable {
throw ex;
}
}
// tag::user_guide[]
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.exception;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler;
// @formatter:off
// tag::user_guide[]
class RecordStateOnErrorExtension implements LifecycleMethodExecutionExceptionHandler {
@Override
public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex)
throws Throwable {
memoryDumpForFurtherInvestigation("Failure recorded during class setup");
throw ex;
}
@Override
public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex)
throws Throwable {
memoryDumpForFurtherInvestigation("Failure recorded during test setup");
throw ex;
}
@Override
public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable ex)
throws Throwable {
memoryDumpForFurtherInvestigation("Failure recorded during test cleanup");
throw ex;
}
@Override
public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable ex)
throws Throwable {
memoryDumpForFurtherInvestigation("Failure recorded during class cleanup");
throw ex;
}
// end::user_guide[]
private void memoryDumpForFurtherInvestigation(String error) {
}
// tag::user_guide[]
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/exception/UncaughtExceptionHandlingDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.exception;
import example.util.Calculator;
import extensions.ExpectToFail;
import org.junit.jupiter.api.Test;
class UncaughtExceptionHandlingDemo {
// tag::user_guide[]
private final Calculator calculator = new Calculator();
// end::user_guide[]
@ExpectToFail
// tag::user_guide[]
@Test
void failsDueToUncaughtException() {
// The following throws an ArithmeticException due to division by
// zero, which causes a test failure.
calculator.divide(1, 0);
}
// end::user_guide[]
}
================================================
FILE: documentation/src/test/java/example/exception/package-info.java
================================================
@NullMarked
package example.exception;
import org.jspecify.annotations.NullMarked;
================================================
FILE: documentation/src/test/java/example/extensions/HttpServerExtension.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.extensions;
import java.io.IOException;
import java.io.UncheckedIOException;
import com.sun.net.httpserver.HttpServer;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
// tag::user_guide[]
public class HttpServerExtension implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return HttpServer.class.equals(parameterContext.getParameter().getType());
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
ExtensionContext rootContext = extensionContext.getRoot();
ExtensionContext.Store store = rootContext.getStore(Namespace.GLOBAL);
Class key = HttpServerResource.class;
HttpServerResource resource = store.computeIfAbsent(key, __ -> {
try {
HttpServerResource serverResource = new HttpServerResource(0);
serverResource.start();
return serverResource;
}
catch (IOException e) {
throw new UncheckedIOException("Failed to create HttpServerResource", e);
}
}, HttpServerResource.class);
return resource.getHttpServer();
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/extensions/HttpServerResource.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.extensions;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpServer;
/**
* Demonstrates an implementation of {@link AutoCloseable} using an {@link HttpServer}.
*/
// tag::user_guide[]
class HttpServerResource implements AutoCloseable {
private final HttpServer httpServer;
// end::user_guide[]
/**
* Initializes the Http server resource, using the given port.
*
* @param port (int) The port number for the server, must be in the range 0-65535.
* @throws IOException if an IOException occurs during initialization.
*/
// tag::user_guide[]
HttpServerResource(int port) throws IOException {
InetAddress loopbackAddress = InetAddress.getLoopbackAddress();
this.httpServer = HttpServer.create(new InetSocketAddress(loopbackAddress, port), 0);
}
HttpServer getHttpServer() {
return httpServer;
}
// end::user_guide[]
/**
* Starts the Http server with an example handler.
*/
// tag::user_guide[]
void start() {
// Example handler
httpServer.createContext("/example", exchange -> {
String body = "This is a test";
exchange.sendResponseHeaders(200, body.length());
try (OutputStream os = exchange.getResponseBody()) {
os.write(body.getBytes(UTF_8));
}
});
httpServer.setExecutor(null);
httpServer.start();
}
@Override
public void close() {
httpServer.stop(0);
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/extensions/ParameterResolverConflictDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.extensions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import extensions.ExpectToFail;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
// tag::user_guide[]
public class ParameterResolverConflictDemo {
// end::user_guide[]
@ExpectToFail
// tag::user_guide[]
@Test
@ExtendWith({ FirstIntegerResolver.class, SecondIntegerResolver.class })
void testInt(int i) {
// Test will not run due to ParameterResolutionException
assertEquals(1, i);
}
static class FirstIntegerResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return parameterContext.getParameter().getType() == int.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return 1;
}
}
static class SecondIntegerResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return parameterContext.getParameter().getType() == int.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return 2;
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/extensions/ParameterResolverCustomAnnotationDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.extensions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
// tag::user_guide[]
public class ParameterResolverCustomAnnotationDemo {
@Test
void testInt(@FirstInteger Integer first, @SecondInteger Integer second) {
assertEquals(1, first);
assertEquals(2, second);
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(FirstInteger.Extension.class)
public @interface FirstInteger {
class Extension implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return parameterContext.getParameter().getType().equals(Integer.class)
&& !parameterContext.isAnnotated(SecondInteger.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return 1;
}
}
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SecondInteger.Extension.class)
public @interface SecondInteger {
class Extension implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return parameterContext.isAnnotated(SecondInteger.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return 2;
}
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.extensions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
// tag::user_guide[]
public class ParameterResolverCustomTypeDemo {
@Test
@ExtendWith({ FirstIntegerResolver.class, SecondIntegerResolver.class })
void testInt(Integer i, WrappedInteger wrappedInteger) {
assertEquals(1, i);
assertEquals(2, wrappedInteger.value);
}
static class FirstIntegerResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return parameterContext.getParameter().getType().equals(Integer.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return 1;
}
}
static class SecondIntegerResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return parameterContext.getParameter().getType().equals(WrappedInteger.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return new WrappedInteger(2);
}
}
static class WrappedInteger {
private final int value;
WrappedInteger(int value) {
this.value = value;
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/extensions/ParameterResolverNoConflictDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.extensions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
// tag::user_guide[]
public class ParameterResolverNoConflictDemo {
@Test
@ExtendWith(FirstIntegerResolver.class)
void firstResolution(int i) {
assertEquals(1, i);
}
@Test
@ExtendWith(SecondIntegerResolver.class)
void secondResolution(int i) {
assertEquals(2, i);
}
static class FirstIntegerResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return parameterContext.getParameter().getType() == int.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return 1;
}
}
static class SecondIntegerResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return parameterContext.getParameter().getType() == int.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return 2;
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/extensions/Random.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.extensions;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
//tag::user_guide[]
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(RandomNumberExtension.class)
public @interface Random {
}
//end::user_guide[]
================================================
FILE: documentation/src/test/java/example/extensions/RandomNumberDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.extensions;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
// tag::user_guide[]
class RandomNumberDemo {
// Use static randomNumber0 field anywhere in the test class,
// including @BeforeAll or @AfterEach lifecycle methods.
@Random
// end::user_guide[]
@Nullable
// tag::user_guide[]
private static Integer randomNumber0;
// Use randomNumber1 field in test methods and @BeforeEach
// or @AfterEach lifecycle methods.
@Random
private int randomNumber1;
RandomNumberDemo(@Random int randomNumber2) {
// Use randomNumber2 in constructor.
}
@BeforeEach
void beforeEach(@Random int randomNumber3) {
// Use randomNumber3 in @BeforeEach method.
}
@Test
void test(@Random int randomNumber4) {
// Use randomNumber4 in test method.
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/extensions/RandomNumberExtension.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.extensions;
// tag::user_guide[]
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields;
import java.lang.reflect.Field;
import java.util.function.Predicate;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
import org.junit.platform.commons.support.ModifierSupport;
// end::user_guide[]
// @formatter:off
// tag::user_guide[]
class RandomNumberExtension
implements BeforeAllCallback, TestInstancePostProcessor, ParameterResolver {
private final java.util.Random random = new java.util.Random(System.nanoTime());
/**
* Inject a random integer into static fields that are annotated with
* {@code @Random} and can be assigned an integer value.
*/
@Override
public void beforeAll(ExtensionContext context) {
Class> testClass = context.getRequiredTestClass();
injectFields(testClass, null, ModifierSupport::isStatic);
}
/**
* Inject a random integer into non-static fields that are annotated with
* {@code @Random} and can be assigned an integer value.
*/
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
Class> testClass = context.getRequiredTestClass();
injectFields(testClass, testInstance, ModifierSupport::isNotStatic);
}
/**
* Determine if the parameter is annotated with {@code @Random} and can be
* assigned an integer value.
*/
@Override
public boolean supportsParameter(ParameterContext pc, ExtensionContext ec) {
return pc.isAnnotated(Random.class) && isInteger(pc.getParameter().getType());
}
/**
* Resolve a random integer.
*/
@Override
public Integer resolveParameter(ParameterContext pc, ExtensionContext ec) {
return this.random.nextInt();
}
private void injectFields(Class> testClass, @Nullable Object testInstance,
Predicate predicate) {
predicate = predicate.and(field -> isInteger(field.getType()));
findAnnotatedFields(testClass, Random.class, predicate)
.forEach(field -> {
try {
field.setAccessible(true);
field.set(testInstance, this.random.nextInt());
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
});
}
private static boolean isInteger(Class> type) {
return type == Integer.class || type == int.class;
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/extensions/package-info.java
================================================
@NullMarked
package example.extensions;
import org.jspecify.annotations.NullMarked;
================================================
FILE: documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.interceptor;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.SwingUtilities;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.InvocationInterceptor;
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
// @formatter:off
// tag::user_guide[]
public class SwingEdtInterceptor implements InvocationInterceptor {
//end::user_guide[]
@SuppressWarnings("NullAway")
//tag::user_guide[]
@Override
public void interceptTestMethod(Invocation invocation,
ReflectiveInvocationContext invocationContext,
ExtensionContext extensionContext) throws Throwable {
AtomicReference throwable = new AtomicReference<>();
SwingUtilities.invokeAndWait(() -> {
try {
invocation.proceed();
}
catch (Throwable t) {
throwable.set(t);
}
});
Throwable t = throwable.get();
if (t != null) {
throw t;
}
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/interceptor/package-info.java
================================================
@NullMarked
package example.interceptor;
import org.jspecify.annotations.NullMarked;
================================================
FILE: documentation/src/test/java/example/package-info.java
================================================
@NullMarked
package example;
import org.jspecify.annotations.NullMarked;
================================================
FILE: documentation/src/test/java/example/registration/DocumentationDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.registration;
import java.nio.file.Path;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.RegisterExtension;
//tag::user_guide[]
class DocumentationDemo {
//end::user_guide[]
@Nullable
//tag::user_guide[]
static Path lookUpDocsDir() {
// return path to docs dir
// end::user_guide[]
return null;
// tag::user_guide[]
}
@RegisterExtension
DocumentationExtension docs = DocumentationExtension.forPath(lookUpDocsDir());
@Test
void generateDocumentation() {
// use this.docs ...
}
}
//end::user_guide[]
@NullMarked
class DocumentationExtension implements AfterEachCallback {
@SuppressWarnings("unused")
private final @Nullable Path path;
private DocumentationExtension(@Nullable Path path) {
this.path = path;
}
static DocumentationExtension forPath(@Nullable Path path) {
return new DocumentationExtension(path);
}
@Override
public void afterEach(ExtensionContext context) {
/* no-op for demo */
}
}
================================================
FILE: documentation/src/test/java/example/registration/WebServerDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.registration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
// tag::user_guide[]
class WebServerDemo {
// end::user_guide[]
// @formatter:off
// tag::user_guide[]
@RegisterExtension
static WebServerExtension server = WebServerExtension.builder()
.enableSecurity(false)
.build();
// end::user_guide[]
// @formatter:on
// tag::user_guide[]
@Test
void getProductList() {
// end::user_guide[]
@SuppressWarnings("resource")
// tag::user_guide[]
WebClient webClient = new WebClient();
String serverUrl = server.getServerUrl();
// Use WebClient to connect to web server using serverUrl and verify response
assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus());
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/session/CloseableHttpServer.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.session;
//tag::user_guide[]
import java.util.concurrent.ExecutorService;
import com.sun.net.httpserver.HttpServer;
public class CloseableHttpServer implements AutoCloseable {
private final HttpServer server;
private final ExecutorService executorService;
CloseableHttpServer(HttpServer server, ExecutorService executorService) {
this.server = server;
this.executorService = executorService;
}
public HttpServer getServer() {
return server;
}
@Override
public void close() { // <1>
server.stop(0); // <2>
executorService.shutdownNow();
}
}
//end::user_guide[]
================================================
FILE: documentation/src/test/java/example/session/GlobalSetupTeardownListener.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.session;
//tag::user_guide[]
import static java.net.InetAddress.getLoopbackAddress;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.sun.net.httpserver.HttpServer;
import org.junit.platform.engine.support.store.Namespace;
import org.junit.platform.engine.support.store.NamespacedHierarchicalStore;
import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.LauncherSessionListener;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestPlan;
public class GlobalSetupTeardownListener implements LauncherSessionListener {
@Override
public void launcherSessionOpened(LauncherSession session) {
// Avoid setup for test discovery by delaying it until tests are about to be executed
session.getLauncher().registerTestExecutionListeners(new TestExecutionListener() {
@Override
public void testPlanExecutionStarted(TestPlan testPlan) {
//end::user_guide[]
if (!testPlan.getConfigurationParameters().getBoolean("enableHttpServer").orElse(true)) {
// avoid starting multiple HTTP servers unnecessarily from UsingTheLauncherDemo
return;
}
//tag::user_guide[]
NamespacedHierarchicalStore store = session.getStore(); // <1>
store.computeIfAbsent(Namespace.GLOBAL, "httpServer", key -> { // <2>
InetSocketAddress address = new InetSocketAddress(getLoopbackAddress(), 0);
HttpServer server;
try {
server = HttpServer.create(address, 0);
}
catch (IOException e) {
throw new UncheckedIOException("Failed to start HTTP server", e);
}
server.createContext("/test", exchange -> {
exchange.sendResponseHeaders(204, -1);
exchange.close();
});
ExecutorService executorService = Executors.newCachedThreadPool();
server.setExecutor(executorService);
server.start(); // <3>
return new CloseableHttpServer(server, executorService);
});
}
});
}
}
//end::user_guide[]
================================================
FILE: documentation/src/test/java/example/session/HttpTests.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.session;
//tag::user_guide[]
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import com.sun.net.httpserver.HttpServer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
@ExtendWith(HttpServerParameterResolver.class)
class HttpTests {
@Test
void respondsWith204(HttpServer server) throws IOException {
String host = server.getAddress().getHostString(); // <2>
int port = server.getAddress().getPort(); // <3>
URL url = URI.create("http://" + host + ":" + port + "/test").toURL();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode(); // <4>
assertEquals(204, responseCode); // <5>
}
}
class HttpServerParameterResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return HttpServer.class.equals(parameterContext.getParameter().getType());
}
//end::user_guide[]
@SuppressWarnings("DataFlowIssue")
//tag::user_guide[]
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return extensionContext
// tag::custom_line_break[]
.getStore(ExtensionContext.Namespace.GLOBAL)
// tag::custom_line_break[]
.get("httpServer", CloseableHttpServer.class) // <1>
.getServer();
}
}
//end::user_guide[]
================================================
FILE: documentation/src/test/java/example/session/package-info.java
================================================
@NullMarked
package example.session;
import org.jspecify.annotations.NullMarked;
================================================
FILE: documentation/src/test/java/example/sharedresources/ChildrenSharedResourcesDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.sharedresources;
import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT;
import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ;
import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE;
import static org.junit.jupiter.api.parallel.ResourceLockTarget.CHILDREN;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ResourceLock;
// tag::user_guide[]
@Execution(CONCURRENT)
@ResourceLock(value = "a", mode = READ, target = CHILDREN)
public class ChildrenSharedResourcesDemo {
@ResourceLock(value = "a", mode = READ_WRITE)
@Test
void test1() throws InterruptedException {
Thread.sleep(2000L);
}
@Test
void test2() throws InterruptedException {
Thread.sleep(2000L);
}
@Test
void test3() throws InterruptedException {
Thread.sleep(2000L);
}
@Test
void test4() throws InterruptedException {
Thread.sleep(2000L);
}
@Test
void test5() throws InterruptedException {
Thread.sleep(2000L);
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.sharedresources;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT;
import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ;
import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE;
import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ResourceAccessMode;
import org.junit.jupiter.api.parallel.ResourceLock;
import org.junit.jupiter.api.parallel.ResourceLocksProvider;
// tag::user_guide[]
@Execution(CONCURRENT)
@ResourceLock(providers = DynamicSharedResourcesDemo.Provider.class)
class DynamicSharedResourcesDemo {
private Properties backup;
@BeforeEach
void backup() {
backup = new Properties();
backup.putAll(System.getProperties());
}
@AfterEach
void restore() {
System.setProperties(backup);
}
@Test
void customPropertyIsNotSetByDefault() {
assertNull(System.getProperty("my.prop"));
}
@Test
void canSetCustomPropertyToApple() {
System.setProperty("my.prop", "apple");
assertEquals("apple", System.getProperty("my.prop"));
}
@Test
void canSetCustomPropertyToBanana() {
System.setProperty("my.prop", "banana");
assertEquals("banana", System.getProperty("my.prop"));
}
static class Provider implements ResourceLocksProvider {
@Override
public Set provideForMethod(List> enclosingInstanceTypes, Class> testClass,
Method testMethod) {
ResourceAccessMode mode = testMethod.getName().startsWith("canSet") ? READ_WRITE : READ;
return Set.of(new Lock(SYSTEM_PROPERTIES, mode));
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/sharedresources/SharedResourceDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.sharedresources;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;
import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.discoveryRequest;
import example.FirstCustomEngine;
import example.SecondCustomEngine;
import org.junit.jupiter.api.Test;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.core.LauncherConfig;
import org.junit.platform.launcher.core.LauncherFactory;
class SharedResourceDemo {
@SuppressWarnings("DataFlowIssue")
//tag::user_guide[]
@Test
void runBothCustomEnginesTest() {
FirstCustomEngine firstCustomEngine = new FirstCustomEngine();
SecondCustomEngine secondCustomEngine = new SecondCustomEngine();
LauncherConfig launcherConfig = LauncherConfig.builder()
// tag::custom_line_break[]
.addTestEngines(firstCustomEngine, secondCustomEngine)
// tag::custom_line_break[]
.enableTestEngineAutoRegistration(false)
// tag::custom_line_break[]
.build();
LauncherDiscoveryRequest discoveryRequest = discoveryRequest()
// tag::custom_line_break[]
.selectors(selectPackage("com.example.mytests"))
// tag::custom_line_break[]
.build();
Launcher launcher = LauncherFactory.create(launcherConfig);
launcher.execute(discoveryRequest);
assertSame(firstCustomEngine.getSocket(), secondCustomEngine.getSocket());
assertTrue(firstCustomEngine.getSocket().isClosed(), "socket should be closed");
}
//end::user_guide[]
}
================================================
FILE: documentation/src/test/java/example/sharedresources/StaticSharedResourcesDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.sharedresources;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT;
import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ;
import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE;
import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES;
import java.util.Properties;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ResourceLock;
// tag::user_guide[]
@Execution(CONCURRENT)
class StaticSharedResourcesDemo {
private Properties backup;
@BeforeEach
void backup() {
backup = new Properties();
backup.putAll(System.getProperties());
}
@AfterEach
void restore() {
System.setProperties(backup);
}
@Test
@ResourceLock(value = SYSTEM_PROPERTIES, mode = READ)
void customPropertyIsNotSetByDefault() {
assertNull(System.getProperty("my.prop"));
}
@Test
@ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
void canSetCustomPropertyToApple() {
System.setProperty("my.prop", "apple");
assertEquals("apple", System.getProperty("my.prop"));
}
@Test
@ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
void canSetCustomPropertyToBanana() {
System.setProperty("my.prop", "banana");
assertEquals("banana", System.getProperty("my.prop"));
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/sharedresources/package-info.java
================================================
@NullMarked
package example.sharedresources;
import org.jspecify.annotations.NullMarked;
================================================
FILE: documentation/src/test/java/example/testinterface/TestInterfaceDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.testinterface;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
// @formatter:off
// tag::user_guide[]
class TestInterfaceDemo implements TestLifecycleLogger,
TimeExecutionLogger, TestInterfaceDynamicTestsDemo {
@Test
void isEqualValue() {
assertEquals(1, "a".length(), "is always equal");
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.testinterface;
import static example.util.StringUtils.isPalindrome;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
// @formatter:off
// tag::user_guide[]
interface TestInterfaceDynamicTestsDemo {
@TestFactory
default Stream dynamicTestsForPalindromes() {
return Stream.of("racecar", "radar", "mom", "dad")
.map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))));
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/testinterface/TestLifecycleLogger.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.testinterface;
import java.util.logging.Logger;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
// @formatter:off
// tag::user_guide[]
@TestInstance(Lifecycle.PER_CLASS)
interface TestLifecycleLogger {
Logger logger = Logger.getLogger(TestLifecycleLogger.class.getName());
@BeforeAll
default void beforeAllTests() {
logger.info("Before all tests");
}
@AfterAll
default void afterAllTests() {
logger.info("After all tests");
}
@BeforeEach
default void beforeEachTest(TestInfo testInfo) {
logger.info(() -> "About to execute [%s]".formatted(
testInfo.getDisplayName()));
}
@AfterEach
default void afterEachTest(TestInfo testInfo) {
logger.info(() -> "Finished executing [%s]".formatted(
testInfo.getDisplayName()));
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/testinterface/TimeExecutionLogger.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.testinterface;
import example.timing.TimingExtension;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ExtendWith;
//tag::user_guide[]
@Tag("timed")
@ExtendWith(TimingExtension.class)
interface TimeExecutionLogger {
}
//end::user_guide[]
================================================
FILE: documentation/src/test/java/example/testinterface/package-info.java
================================================
@NullMarked
package example.testinterface;
import org.jspecify.annotations.NullMarked;
================================================
FILE: documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.testkit;
// @formatter:off
// tag::user_guide[]
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason;
import static org.junit.platform.testkit.engine.EventConditions.container;
import static org.junit.platform.testkit.engine.EventConditions.engine;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully;
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason;
import static org.junit.platform.testkit.engine.EventConditions.started;
import static org.junit.platform.testkit.engine.EventConditions.test;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
import java.io.StringWriter;
import java.io.Writer;
import example.ExampleTestCase;
import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.opentest4j.TestAbortedException;
class EngineTestKitAllEventsDemo {
@Test
void verifyAllJupiterEvents() {
Writer writer = // create a java.io.Writer for debug output
// end::user_guide[]
// For the demo, we are swallowing the debug output.
new StringWriter();
// tag::user_guide[]
EngineTestKit.engine("junit-jupiter") // <1>
.selectors(selectClass(ExampleTestCase.class)) // <2>
.execute() // <3>
.allEvents() // <4>
.debug(writer) // <5>
.assertEventsMatchExactly( // <6>
event(engine(), started()),
event(container(ExampleTestCase.class), started()),
event(test("skippedTest"), skippedWithReason("for demonstration purposes")),
event(test("succeedingTest"), started()),
event(test("succeedingTest"), finishedSuccessfully()),
event(test("abortedTest"), started()),
event(test("abortedTest"),
abortedWithReason(instanceOf(TestAbortedException.class),
message(m -> m.contains("abc does not contain Z")))),
event(test("failingTest"), started()),
event(test("failingTest"), finishedWithFailure(
instanceOf(ArithmeticException.class), message(it -> it.endsWith("by zero")))),
event(container(ExampleTestCase.class), finishedSuccessfully()),
event(engine(), finishedSuccessfully()));
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/testkit/EngineTestKitDiscoveryDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.testkit;
// tag::user_guide[]
import static java.util.Collections.emptyList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import example.ExampleTestCase;
import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineDiscoveryResults;
import org.junit.platform.testkit.engine.EngineTestKit;
class EngineTestKitDiscoveryDemo {
@Test
void verifyJupiterDiscovery() {
EngineDiscoveryResults results = EngineTestKit.engine("junit-jupiter") // <1>
.selectors(selectClass(ExampleTestCase.class)) // <2>
.discover(); // <3>
assertEquals("JUnit Jupiter", results.getEngineDescriptor().getDisplayName()); // <4>
assertEquals(emptyList(), results.getDiscoveryIssues()); // <5>
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.testkit;
// @formatter:off
// tag::user_guide[]
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
import static org.junit.platform.testkit.engine.EventConditions.test;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
import example.ExampleTestCase;
import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;
class EngineTestKitFailedMethodDemo {
@Test
void verifyJupiterMethodFailed() {
EngineTestKit.engine("junit-jupiter") // <1>
.selectors(selectClass(ExampleTestCase.class)) // <2>
.execute() // <3>
.testEvents() // <4>
.assertThatEvents().haveExactly(1, // <5>
event(test("failingTest"),
finishedWithFailure(
instanceOf(ArithmeticException.class), message(it -> it.endsWith("by zero")))));
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.testkit;
// @formatter:off
// tag::user_guide[]
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason;
import static org.junit.platform.testkit.engine.EventConditions.test;
import example.ExampleTestCase;
import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.junit.platform.testkit.engine.Events;
class EngineTestKitSkippedMethodDemo {
@Test
void verifyJupiterMethodWasSkipped() {
String methodName = "skippedTest";
Events testEvents = EngineTestKit // <5>
.engine("junit-jupiter") // <1>
.selectors(selectMethod(ExampleTestCase.class, methodName)) // <2>
.execute() // <3>
.testEvents(); // <4>
testEvents.assertStatistics(stats -> stats.skipped(1)); // <6>
testEvents.assertThatEvents() // <7>
.haveExactly(1, event(test(methodName),
skippedWithReason("for demonstration purposes")));
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.testkit;
// @formatter:off
// tag::user_guide[]
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import example.ExampleTestCase;
import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;
class EngineTestKitStatisticsDemo {
@Test
void verifyJupiterContainerStats() {
EngineTestKit
.engine("junit-jupiter") // <1>
.selectors(selectClass(ExampleTestCase.class)) // <2>
.execute() // <3>
.containerEvents() // <4>
.assertStatistics(stats -> stats.started(2).succeeded(2)); // <5>
}
@Test
void verifyJupiterTestStats() {
EngineTestKit
.engine("junit-jupiter") // <1>
.selectors(selectClass(ExampleTestCase.class)) // <2>
.execute() // <3>
.testEvents() // <6>
.assertStatistics(stats ->
stats.skipped(1).started(3).succeeded(1).aborted(1).failed(1)); // <7>
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/testkit/package-info.java
================================================
@NullMarked
package example.testkit;
import org.jspecify.annotations.NullMarked;
================================================
FILE: documentation/src/test/java/example/timing/TimingExtension.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.timing;
// tag::user_guide[]
import java.lang.reflect.Method;
import java.util.logging.Logger;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
// end::user_guide[]
/**
* Simple extension that times the execution of test methods and
* logs the results at {@code INFO} level.
*
* @since 5.0
*/
// @formatter:off
// tag::user_guide[]
public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
private static final Logger logger = Logger.getLogger(TimingExtension.class.getName());
private static final String START_TIME = "start time";
@Override
public void beforeTestExecution(ExtensionContext context) {
getStore(context).put(START_TIME, System.currentTimeMillis());
}
//end::user_guide[]
@SuppressWarnings("DataFlowIssue")
//tag::user_guide[]
@Override
public void afterTestExecution(ExtensionContext context) {
Method testMethod = context.getRequiredTestMethod();
long startTime = getStore(context).remove(START_TIME, long.class);
long duration = System.currentTimeMillis() - startTime;
logger.info(() ->
"Method [%s] took %s ms.".formatted(testMethod.getName(), duration));
}
private Store getStore(ExtensionContext context) {
return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod()));
}
}
// end::user_guide[]
// @formatter:on
================================================
FILE: documentation/src/test/java/example/timing/TimingExtensionTests.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.timing;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
/**
* Tests that demonstrate the example {@link TimingExtension}.
*
* @since 5.0
*/
// tag::user_guide[]
@ExtendWith(TimingExtension.class)
class TimingExtensionTests {
@Test
void sleep20ms() throws Exception {
Thread.sleep(20);
}
@Test
void sleep50ms() throws Exception {
Thread.sleep(50);
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/java/example/timing/package-info.java
================================================
@NullMarked
package example.timing;
import org.jspecify.annotations.NullMarked;
================================================
FILE: documentation/src/test/java/extensions/DisabledOnOpenJ9.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package extensions;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@DisabledIfSystemProperty(named = "java.vm.vendor", matches = ".*OpenJ9.*")
public @interface DisabledOnOpenJ9 {
}
================================================
FILE: documentation/src/test/java/extensions/ExpectToFail.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package extensions;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(ExpectToFail.Extension.class)
public @interface ExpectToFail {
class Extension implements TestExecutionExceptionHandler, AfterEachCallback {
private static final String KEY = "exception";
@Override
public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
getExceptionStore(context).put(KEY, throwable);
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
assertNotNull(getExceptionStore(context).get(KEY), "Test should have failed");
}
private Store getExceptionStore(ExtensionContext context) {
return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod()));
}
}
}
================================================
FILE: documentation/src/test/java/extensions/package-info.java
================================================
@NullMarked
package extensions;
import org.jspecify.annotations.NullMarked;
================================================
FILE: documentation/src/test/kotlin/example/FibonacciCalculator.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example
class FibonacciCalculator {
private val fibonacci =
sequence {
yield(0) // 0th Fibonacci number
yield(1) // first Fibonacci number
var cur = 1
var next = 1
while (true) {
yield(next) // next Fibonacci number
val tmp = cur + next
cur = next
next = tmp
}
}
fun fib(fibonacciNumber: Int) = fibonacci.elementAt(fibonacciNumber)
}
================================================
FILE: documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example
// tag::user_guide[]
import example.domain.Person
import example.util.Calculator
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertInstanceOf
import org.junit.jupiter.api.assertNotNull
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.assertTimeout
import org.junit.jupiter.api.assertTimeoutPreemptively
import java.time.Duration
class KotlinAssertionsDemo {
private val person = Person("Jane", "Doe")
private val people = setOf(person, Person("John", "Doe"))
@Test
fun `exception absence testing`() {
val calculator = Calculator()
val result =
assertDoesNotThrow("Should not throw an exception") {
calculator.divide(0, 1)
}
assertEquals(0, result)
}
// end::user_guide[]
@extensions.DisabledOnOpenJ9
// tag::user_guide[]
@Test
fun `expected exception testing`() {
val calculator = Calculator()
val exception =
assertThrows ("Should throw an exception") {
calculator.divide(1, 0)
}
assertEquals("/ by zero", exception.message)
}
@Test
fun `grouped assertions`() {
assertAll(
"Person properties",
{ assertEquals("Jane", person.firstName) },
{ assertEquals("Doe", person.lastName) }
)
}
@Test
fun `grouped assertions from a stream`() {
assertAll(
"People with first name starting with J",
people
.stream()
.map {
// This mapping returns Stream<() -> Unit>
{ assertTrue(it.firstName.startsWith("J")) }
}
)
}
@Test
fun `grouped assertions from a collection`() {
assertAll(
"People with last name of Doe",
people.map { { assertEquals("Doe", it.lastName) } }
)
}
// end::user_guide[]
@Tag("timeout")
// tag::user_guide[]
@Test
fun `timeout not exceeded testing`() {
val fibonacciCalculator = FibonacciCalculator()
val result =
assertTimeout(Duration.ofMillis(1000)) {
fibonacciCalculator.fib(14)
}
assertEquals(377, result)
}
// end::user_guide[]
@Tag("timeout")
@extensions.ExpectToFail
// tag::user_guide[]
@Test
fun `timeout exceeded with preemptive termination`() {
// The following assertion fails with an error message similar to:
// execution timed out after 10 ms
assertTimeoutPreemptively(Duration.ofMillis(10)) {
// Simulate task that takes more than 10 ms.
Thread.sleep(100)
}
}
@Test
fun `assertNotNull with a smart cast`() {
val nullablePerson: Person? = person
assertNotNull(nullablePerson)
// The compiler smart casts nullablePerson to a non-nullable object.
// The safe call operator (?.) isn't required.
assertEquals(person.firstName, nullablePerson.firstName)
assertEquals(person.lastName, nullablePerson.lastName)
}
@Test
fun `assertInstanceOf with a smart cast`() {
val maybePerson: Any = person
assertInstanceOf(maybePerson)
// The compiler smart casts maybePerson to a Person object,
// allowing to access the Person properties.
assertEquals(person.firstName, maybePerson.firstName)
assertEquals(person.lastName, maybePerson.lastName)
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/KotlinCoroutinesDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example
// tag::user_guide[]
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
// end::user_guide[]
// @formatter:off
@Suppress("JUnitMalformedDeclaration")
// tag::user_guide[]
class KotlinCoroutinesDemo {
// end::user_guide[]
// @formatter:on
// tag::user_guide[]
@BeforeEach
fun regularSetUp() {
}
@BeforeEach
suspend fun coroutineSetUp() {
}
@Test
fun regularTest() {
}
@Test
suspend fun coroutineTest() {
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/KotlinCoroutinesRunTestDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example
// tag::user_guide[]
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class KotlinCoroutinesRunTestDemo {
// end::user_guide[]
// @formatter:off
// tag::user_guide[]
@Test
fun coroutineTestUsingRunTest() = runTest {
// ...
}
// end::user_guide[]
// @formatter:on
// tag::user_guide[]
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/AssumptionsDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
// tag::user_guide[]
import example.util.Calculator
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.Assumptions.assumingThat
import org.junit.jupiter.api.Test
class AssumptionsDemo {
private val calculator = Calculator()
@Test
fun testOnlyOnCiServer() {
assumeTrue(System.getenv("ENV") == "CI")
// remainder of test
}
@Test
fun testOnlyOnDeveloperWorkstation() {
assumeTrue(System.getenv("ENV") == "DEV") {
"Aborting test: not on developer workstation"
}
// remainder of test
}
@Test
fun testInAllEnvironments() {
assumingThat(System.getenv("ENV") == "CI") {
// perform these assertions only on the CI server
assertEquals(2, calculator.divide(4, 2))
}
// perform these assertions in all environments
assertEquals(42, calculator.multiply(6, 7))
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/AutoCloseDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
import example.registration.WebClient
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.AutoClose
import org.junit.jupiter.api.Test
// tag::user_guide_example[]
class AutoCloseDemo {
@AutoClose // <1>
val webClient = WebClient() // <2>
val serverUrl = // specify server URL ...
// end::user_guide_example[]
"https://localhost"
// tag::user_guide_example[]
@Test
fun getProductList() {
// Use WebClient to connect to web server and verify response
assertEquals(200, webClient.get("$serverUrl/products").responseStatus)
}
}
// end::user_guide_example[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/ConditionalTestExecutionDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.condition.DisabledForJreRange
import org.junit.jupiter.api.condition.DisabledIf
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable
import org.junit.jupiter.api.condition.DisabledIfSystemProperty
import org.junit.jupiter.api.condition.DisabledInNativeImage
import org.junit.jupiter.api.condition.DisabledOnJre
import org.junit.jupiter.api.condition.DisabledOnOs
import org.junit.jupiter.api.condition.EnabledForJreRange
import org.junit.jupiter.api.condition.EnabledIf
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable
import org.junit.jupiter.api.condition.EnabledIfSystemProperty
import org.junit.jupiter.api.condition.EnabledInNativeImage
import org.junit.jupiter.api.condition.EnabledOnJre
import org.junit.jupiter.api.condition.EnabledOnOs
import org.junit.jupiter.api.condition.JRE.JAVA_17
import org.junit.jupiter.api.condition.JRE.JAVA_18
import org.junit.jupiter.api.condition.JRE.JAVA_19
import org.junit.jupiter.api.condition.JRE.JAVA_21
import org.junit.jupiter.api.condition.JRE.JAVA_25
import org.junit.jupiter.api.condition.OS.LINUX
import org.junit.jupiter.api.condition.OS.MAC
import org.junit.jupiter.api.condition.OS.WINDOWS
class ConditionalTestExecutionDemo {
// tag::user_guide_os[]
@Test
@EnabledOnOs(MAC)
fun onlyOnMacOs() {
// ...
}
@TestOnMac
fun testOnMac() {
// ...
}
@Test
@EnabledOnOs(LINUX, MAC)
fun onLinuxOrMac() {
// ...
}
@Test
@DisabledOnOs(WINDOWS)
fun notOnWindows() {
// ...
}
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@Test
@EnabledOnOs(MAC)
annotation class TestOnMac
// end::user_guide_os[]
// tag::user_guide_architecture[]
@Test
@EnabledOnOs(architectures = ["aarch64"])
fun onAarch64() {
// ...
}
@Test
@DisabledOnOs(architectures = ["x86_64"])
fun notOnX86_64() {
// ...
}
@Test
@EnabledOnOs(value = [MAC], architectures = ["aarch64"])
fun onNewMacs() {
// ...
}
@Test
@DisabledOnOs(value = [MAC], architectures = ["aarch64"])
fun notOnNewMacs() {
// ...
}
// end::user_guide_architecture[]
// tag::user_guide_jre[]
@Test
@EnabledOnJre(JAVA_17)
fun onlyOnJava17() {
// ...
}
@Test
@EnabledOnJre(JAVA_17, JAVA_21)
fun onJava17And21() {
// ...
}
@Test
@EnabledForJreRange(min = JAVA_21, max = JAVA_25)
fun fromJava21To25() {
// ...
}
@Test
@EnabledForJreRange(min = JAVA_21)
fun onJava21ndHigher() {
// ...
}
@Test
@EnabledForJreRange(max = JAVA_18)
fun fromJava17To18() {
// ...
}
@Test
@DisabledOnJre(JAVA_19)
fun notOnJava19() {
// ...
}
@Test
@DisabledForJreRange(min = JAVA_17, max = JAVA_17)
fun notFromJava17To19() {
// ...
}
@Test
@DisabledForJreRange(min = JAVA_19)
fun notOnJava19AndHigher() {
// ...
}
@Test
@DisabledForJreRange(max = JAVA_18)
fun notFromJava17To18() {
// ...
}
// end::user_guide_jre[]
// tag::user_guide_jre_arbitrary_versions[]
@Test
@EnabledOnJre(versions = [26])
fun onlyOnJava26() {
// ...
}
// Can also be expressed as follows.
// @EnabledOnJre(value = [JAVA_25], versions = [26])
@Test
@EnabledOnJre(versions = [25, 26])
fun onJava25And26() {
// ...
}
@Test
@EnabledForJreRange(minVersion = 26)
fun onJava26AndHigher() {
// ...
}
// Can also be expressed as follows.
// @EnabledForJreRange(min = JAVA_25, maxVersion = 27)
@Test
@EnabledForJreRange(minVersion = 25, maxVersion = 27)
fun fromJava25To27() {
// ...
}
@Test
@DisabledOnJre(versions = [26])
fun notOnJava26() {
// ...
}
// Can also be expressed as follows.
// @DisabledOnJre(value = [JAVA_25], versions = [26])
@Test
@DisabledOnJre(versions = [25, 26])
fun notOnJava25And26() {
// ...
}
@Test
@DisabledForJreRange(minVersion = 26)
fun notOnJava26AndHigher() {
// ...
}
// Can also be expressed as follows.
// @DisabledForJreRange(min = JAVA_25, maxVersion = 27)
@Test
@DisabledForJreRange(minVersion = 25, maxVersion = 27)
fun notFromJava25To27() {
// ...
}
// end::user_guide_jre_arbitrary_versions[]
// tag::user_guide_native[]
@Test
@EnabledInNativeImage
fun onlyWithinNativeImage() {
// ...
}
@Test
@DisabledInNativeImage
fun neverWithinNativeImage() {
// ...
}
// end::user_guide_native[]
// tag::user_guide_system_property[]
@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
fun onlyOn64BitArchitectures() {
// ...
}
@Test
@DisabledIfSystemProperty(named = "ci-server", matches = "true")
fun notOnCiServer() {
// ...
}
// end::user_guide_system_property[]
// tag::user_guide_environment_variable[]
@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server")
fun onlyOnStagingServer() {
// ...
}
@Test
@DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*")
fun notOnDeveloperWorkstation() {
// ...
}
// end::user_guide_environment_variable[]
// tag::user_guide_custom[]
@Test
@EnabledIf("customCondition")
fun enabled() {
// ...
}
@Test
@DisabledIf("customCondition")
fun disabled() {
// ...
}
fun customCondition(): Boolean = true
// end::user_guide_custom[]
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.util.DefaultLocale
import org.junit.jupiter.api.util.DefaultTimeZone
import org.junit.jupiter.api.util.LocaleProvider
import org.junit.jupiter.api.util.TimeZoneProvider
import java.time.ZoneOffset
import java.util.Locale
import java.util.TimeZone
class DefaultLocaleTimezoneExtensionDemo {
// tag::default_locale_language[]
@Test
@DefaultLocale("zh-Hant-TW")
fun test_with_language() {
assertThat(Locale.getDefault()).isEqualTo(Locale.forLanguageTag("zh-Hant-TW"))
}
// end::default_locale_language[]
// tag::default_locale_language_alternatives[]
@Test
@DefaultLocale(language = "en")
fun test_with_language_only() {
assertThat(Locale.getDefault()).isEqualTo(Locale.Builder().setLanguage("en").build())
}
@Test
@DefaultLocale(language = "en", country = "EN")
fun test_with_language_and_country() {
assertThat(Locale.getDefault()).isEqualTo(
Locale
.Builder()
.setLanguage("en")
.setRegion("EN")
.build()
)
}
@Test
@DefaultLocale(language = "ja", country = "JP", variant = "japanese")
fun test_with_language_and_country_and_vairant() {
assertThat(Locale.getDefault()).isEqualTo(
Locale
.Builder()
.setLanguage("ja")
.setRegion("JP")
.setVariant("japanese")
.build()
)
}
// end::default_locale_language_alternatives[]
// @formatter:off
@Nested
// tag::default_locale_class_level[]
@DefaultLocale(language = "fr")
// end::default_locale_class_level[]
inner
// tag::default_locale_class_level[]
class MyLocaleTests {
// end::default_locale_class_level[]
// @formatter:on
// tag::default_locale_class_level[]
@Test
fun test_with_class_level_configuration() {
assertThat(Locale.getDefault()).isEqualTo(Locale.Builder().setLanguage("fr").build())
}
@Test
@DefaultLocale(language = "en")
fun test_with_method_level_configuration() {
assertThat(Locale.getDefault()).isEqualTo(Locale.Builder().setLanguage("en").build())
}
}
// end::default_locale_class_level[]
// tag::default_locale_with_provider[]
@Test
@DefaultLocale(localeProvider = EnglishProvider::class)
fun test_with_locale_provider() {
assertThat(Locale.getDefault()).isEqualTo(Locale.Builder().setLanguage("en").build())
}
class EnglishProvider : LocaleProvider {
override fun get(): Locale = Locale.ENGLISH
}
// end::default_locale_with_provider[]
// tag::default_timezone_zone[]
@Test
@DefaultTimeZone("CET")
fun test_with_short_zone_id() {
assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("CET"))
}
@Test
@DefaultTimeZone("Africa/Juba")
fun test_with_long_zone_id() {
assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("Africa/Juba"))
}
// end::default_timezone_zone[]
// @formatter:off
@Nested
// tag::default_timezone_class_level[]
@DefaultTimeZone("CET")
// end::default_timezone_class_level[]
inner
// tag::default_timezone_class_level[]
class MyTimeZoneTests {
// end::default_timezone_class_level[]
// @formatter:on
// tag::default_timezone_class_level[]
@Test
fun test_with_class_level_configuration() {
assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("CET"))
}
@Test
@DefaultTimeZone("Africa/Juba")
fun test_with_method_level_configuration() {
assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("Africa/Juba"))
}
}
// end::default_timezone_class_level[]
// tag::default_time_zone_with_provider[]
@Test
@DefaultTimeZone(timeZoneProvider = UtcTimeZoneProvider::class)
fun test_with_time_zone_provider() {
assertThat(TimeZone.getDefault()).isEqualTo(TimeZone.getTimeZone("UTC"))
}
class UtcTimeZoneProvider : TimeZoneProvider {
override fun get(): TimeZone = TimeZone.getTimeZone(ZoneOffset.UTC)
}
// end::default_time_zone_with_provider[]
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/DisplayNameDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
// tag::user_guide[]
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
@DisplayName("A special test case")
class DisplayNameDemo {
@Test
@DisplayName("Custom test name containing spaces")
fun testWithDisplayNameContainingSpaces() {
}
@Test
@DisplayName("╯°□°)╯")
fun testWithDisplayNameContainingSpecialCharacters() {
}
@Test
@DisplayName("😱")
fun testWithDisplayNameContainingEmoji() {
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/DisplayNameGeneratorDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.DisplayNameGeneration
import org.junit.jupiter.api.DisplayNameGenerator
import org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences.SentenceFragment
import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores
import org.junit.jupiter.api.IndicativeSentencesGeneration
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
@Suppress("ClassName")
class DisplayNameGeneratorDemo {
// @formatter:off
@Nested
// tag::user_guide_replace_underscores[]
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores::class)
// end::user_guide_replace_underscores[]
inner
// tag::user_guide_replace_underscores[]
class A_year_is_not_supported {
// end::user_guide_replace_underscores[]
// @formatter:on
// tag::user_guide_replace_underscores[]
@Test
fun if_it_is_zero() {
}
@DisplayName("A negative value for year is not supported by the leap year computation.")
@ParameterizedTest(name = "For example, year {0} is not supported.")
@ValueSource(ints = [-1, -4])
fun if_it_is_negative(year: Int) {
}
}
// end::user_guide_replace_underscores[]
// @formatter:off
@Nested
// tag::user_guide_indicative_sentences[]
@IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores::class)
// end::user_guide_indicative_sentences[]
inner
// tag::user_guide_indicative_sentences[]
class A_year_is_a_leap_year {
// end::user_guide_indicative_sentences[]
// @formatter:on
// tag::user_guide_indicative_sentences[]
@Test
fun if_it_is_divisible_by_4_but_not_by_100() {
}
@ParameterizedTest(name = "Year {0} is a leap year.")
@ValueSource(ints = [2016, 2020, 2048])
fun if_it_is_one_of_the_following_years(year: Int) {
}
}
// end::user_guide_indicative_sentences[]
// @formatter:off
@Nested
// tag::user_guide_custom_sentence_fragments[]
@SentenceFragment("A year is a leap year")
@IndicativeSentencesGeneration
// end::user_guide_custom_sentence_fragments[]
inner
// tag::user_guide_custom_sentence_fragments[]
class LeapYearTests {
// end::user_guide_custom_sentence_fragments[]
// @formatter:on
// tag::user_guide_custom_sentence_fragments[]
@SentenceFragment("if it is divisible by 4 but not by 100")
@Test
fun divisibleBy4ButNotBy100() {
}
@SentenceFragment("if it is one of the following years")
@ParameterizedTest(name = "{0}")
@ValueSource(ints = [2016, 2020, 2048])
fun validLeapYear(year: Int) {
}
}
// end::user_guide_custom_sentence_fragments[]
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/DynamicTestsDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
// tag::user_guide[]
import example.util.Calculator
import example.util.StringUtils.isPalindrome
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.DynamicContainer.dynamicContainer
import org.junit.jupiter.api.DynamicNode
import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.DynamicTest.dynamicTest
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.TestFactory
import org.junit.jupiter.api.parallel.Execution
import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
import org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD
import java.util.Random
import java.util.stream.IntStream
import java.util.stream.Stream
// end::user_guide[]
// tag::user_guide[]
class DynamicTestsDemo {
private val calculator = Calculator()
// This method will not be executed but produce a warning
@TestFactory
// end::user_guide[]
@Tag("exclude")
fun dummy(): DynamicTest = dynamicTest("dummy") {}
// tag::user_guide[]
fun dynamicTestsWithInvalidReturnType(): List = listOf("Hello")
@TestFactory
fun dynamicTestsFromCollection(): Collection =
listOf(
dynamicTest("1st dynamic test") { assertTrue(isPalindrome("madam")) },
dynamicTest("2nd dynamic test") { assertEquals(4, calculator.multiply(2, 2)) }
)
@TestFactory
fun dynamicTestsFromIterable(): Iterable =
listOf(
dynamicTest("3rd dynamic test") { assertTrue(isPalindrome("madam")) },
dynamicTest("4th dynamic test") { assertEquals(4, calculator.multiply(2, 2)) }
)
@TestFactory
fun dynamicTestsFromIterator(): Iterator =
listOf(
dynamicTest("5th dynamic test") { assertTrue(isPalindrome("madam")) },
dynamicTest("6th dynamic test") { assertEquals(4, calculator.multiply(2, 2)) }
).iterator()
@TestFactory
fun dynamicTestsFromArray(): Array =
arrayOf(
dynamicTest("7th dynamic test") { assertTrue(isPalindrome("madam")) },
dynamicTest("8th dynamic test") { assertEquals(4, calculator.multiply(2, 2)) }
)
@TestFactory
fun dynamicTestsFromStream(): Stream =
Stream
.of("racecar", "radar", "mom", "dad")
.map { text -> dynamicTest(text) { assertTrue(isPalindrome(text)) } }
@TestFactory
fun dynamicTestsFromSequence(): Sequence =
sequenceOf("racecar", "radar", "mom", "dad")
.map { text -> dynamicTest(text) { assertTrue(isPalindrome(text)) } }
@TestFactory
fun dynamicTestsFromIntStream(): Stream {
// Generates tests for the first 10 even integers.
return IntStream
.iterate(0) { n -> n + 2 }
.limit(10)
.mapToObj { n -> dynamicTest("test$n") { assertEquals(0, n % 2) } }
}
@TestFactory
fun generateRandomNumberOfTests(): Stream {
// Generates random positive integers between 0 and 100 until
// a number evenly divisible by 7 is encountered.
val inputGenerator =
object : Iterator {
var random = Random()
// end::user_guide[]
init {
// Use fixed seed to always produce the same number of tests for execution on the CI server
random = Random(23)
}
// tag::user_guide[]
var current = 0
override fun hasNext(): Boolean {
current = random.nextInt(100)
return current % 7 != 0
}
override fun next(): Int = current
}
// Generates display names like: input:5, input:37, input:85, etc.
val displayNameGenerator = { input: Int -> "input:$input" }
// Executes tests based on the current input value.
val testExecutor = { input: Int -> assertTrue(input % 7 != 0) }
// Returns a stream of dynamic tests.
return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor)
}
@TestFactory
fun dynamicTestsFromStreamFactoryMethod(): Stream {
// Stream of palindromes to check
val inputStream = Stream.of("racecar", "radar", "mom", "dad")
// Generates display names like: racecar is a palindrome
val displayNameGenerator = { text: String -> "$text is a palindrome" }
// Executes tests based on the current input value.
val testExecutor = { text: String -> assertTrue(isPalindrome(text)) }
// Returns a stream of dynamic tests.
return DynamicTest.stream(inputStream, displayNameGenerator, testExecutor)
}
@TestFactory
fun dynamicTestsWithContainers(): Stream =
Stream
.of("A", "B", "C")
.map { input ->
dynamicContainer(
"Container $input",
Stream.of(
dynamicTest("not null") { assertNotNull(input) },
dynamicContainer(
"properties",
Stream.of(
dynamicTest("length > 0") { assertTrue(input.length > 0) },
dynamicTest("not empty") { assertFalse(input.isEmpty()) }
)
)
)
)
}
// end::user_guide[]
// tag::execution_mode[]
@TestFactory
@Execution(CONCURRENT) // <1>
fun dynamicTestsWithConfiguredExecutionMode(): Stream =
Stream
.of("A", "B", "C")
.map { input ->
dynamicContainer { outer ->
outer
.displayName("Container $input")
.children(
dynamicTest { config ->
config
.displayName("not null")
.executionMode(SAME_THREAD) // <2>
.executable { assertNotNull(input) }
},
dynamicContainer { inner ->
inner
.displayName("properties")
.executionMode(CONCURRENT) // <3>
.childExecutionMode(SAME_THREAD) // <4>
.children(
dynamicTest { config ->
config
.displayName("length > 0")
.executionMode(CONCURRENT) // <5>
.executable { assertTrue(input.length > 0) }
},
dynamicTest { config ->
config
.displayName("not empty")
.executable { assertFalse(input.isEmpty()) }
}
)
}
)
}
}
// end::execution_mode[]
// tag::user_guide[]
@TestFactory
fun dynamicNodeSingleTest(): DynamicNode = dynamicTest("'pop' is a palindrome") { assertTrue(isPalindrome("pop")) }
@TestFactory
fun dynamicNodeSingleContainer(): DynamicNode =
dynamicContainer(
"palindromes",
Stream
.of("racecar", "radar", "mom", "dad")
.map { text -> dynamicTest(text) { assertTrue(isPalindrome(text)) } }
)
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/DynamicTestsNamedDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
// tag::user_guide[]
import example.util.StringUtils.isPalindrome
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.Named.named
import org.junit.jupiter.api.NamedExecutable
import org.junit.jupiter.api.TestFactory
import java.util.stream.Stream
class DynamicTestsNamedDemo {
@TestFactory
fun dynamicTestsFromStreamFactoryMethodWithNames(): Stream {
// Stream of palindromes to check
// end::user_guide[]
// tag::user_guide[]
val inputStream =
Stream.of(
named("racecar is a palindrome", "racecar"),
named("radar is also a palindrome", "radar"),
named("mom also seems to be a palindrome", "mom"),
named("dad is yet another palindrome", "dad")
)
// end::user_guide[]
// tag::user_guide[]
// Returns a stream of dynamic tests.
return DynamicTest.stream(inputStream) { text -> assertTrue(isPalindrome(text)) }
}
@TestFactory
fun dynamicTestsFromStreamFactoryMethodWithNamedExecutables(): Stream {
// Stream of palindromes to check
// end::user_guide[]
// tag::user_guide[]
val inputStream =
Stream
.of("racecar", "radar", "mom", "dad")
.map { PalindromeNamedExecutable(it) }
// end::user_guide[]
// tag::user_guide[]
// Returns a stream of dynamic tests based on NamedExecutables.
return DynamicTest.stream(inputStream)
}
class PalindromeNamedExecutable(
private val text: String
) : NamedExecutable {
override fun getName(): String = "'$text' is a palindrome"
override fun execute() {
assertTrue(isPalindrome(text))
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/ExternalCustomConditionDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
// tag::user_guide_external_custom_condition[]
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.condition.EnabledIf
class ExternalCustomConditionDemo {
@Test
@EnabledIf("example.kotlin.ExternalCondition#customCondition")
fun enabled() {
// ...
}
}
class ExternalCondition {
companion object {
@JvmStatic
fun customCondition(): Boolean = true
}
}
// end::user_guide_external_custom_condition[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/Fast.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
// tag::user_guide[]
import org.junit.jupiter.api.Tag
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@Tag("fast")
annotation class Fast
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/FastTest.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
// tag::user_guide[]
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@Tag("fast")
@Test
annotation class FastTest
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/HttpServerDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
import com.sun.net.httpserver.HttpServer
import example.kotlin.extensions.HttpServerExtension
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.net.HttpURLConnection
import java.net.URI
import java.net.URL
// tag::user_guide[]
@ExtendWith(HttpServerExtension::class)
class HttpServerDemo {
// end::user_guide[]
@Suppress("HttpUrlsUsage")
// tag::user_guide[]
@Test
fun httpCall(server: HttpServer) {
val address = server.address
val requestUrl = URI("http://${address.hostName}:${address.port}/example").toURL()
val responseBody = sendRequest(requestUrl)
assertEquals("This is a test", responseBody)
}
private fun sendRequest(url: URL): String {
val connection = url.openConnection() as HttpURLConnection
val contentLength = connection.contentLength
url.openStream().use { response ->
val content = ByteArray(contentLength)
assertEquals(contentLength, response.read(content))
return String(content, Charsets.UTF_8)
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/MyFirstJUnitJupiterTests.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
// tag::user_guide[]
import example.util.Calculator
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class MyFirstJUnitJupiterTests {
private val calculator = Calculator()
@Test
fun addition() {
assertEquals(2, calculator.add(1, 1))
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/MyRandomParametersTest.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
import example.extensions.Random
import org.junit.jupiter.api.Assertions.assertNotEquals
import org.junit.jupiter.api.Test
// tag::user_guide[]
class MyRandomParametersTest(
@Random randomNumber: Int
) {
@Test
fun injectsInteger(
@Random i: Int,
@Random j: Int
) {
assertNotEquals(i, j)
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/ParameterizedClassDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.parallel.Execution
import org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD
import org.junit.jupiter.params.Parameter
import org.junit.jupiter.params.ParameterizedClass
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import java.time.Duration
class ParameterizedClassDemo {
// @formatter:off
@Nested
// tag::nested[]
@Execution(SAME_THREAD)
@ParameterizedClass
@ValueSource(strings = ["apple", "banana"])
// end::nested[]
inner
// tag::nested[]
class FruitTests {
// end::nested[]
// @formatter:on
// tag::nested[]
@Parameter
lateinit var fruit: String
@Nested
@ParameterizedClass
@ValueSource(ints = [23, 42])
inner class QuantityTests {
@Parameter
var quantity: Int = 0
@ParameterizedTest
@ValueSource(strings = ["PT1H", "PT2H"])
fun test(duration: Duration) {
assertFruit(fruit)
assertQuantity(quantity)
assertFalse(duration.isNegative)
}
}
}
// end::nested[]
private fun assertFruit(fruit: String) {
assertTrue(
listOf("apple", "banana", "cherry", "dewberry").contains(fruit)
) { "not a fruit: $fruit" }
}
private fun assertQuantity(quantity: Int) {
assertTrue(quantity > 0)
}
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/PollingTimeoutDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
class PollingTimeoutDemo {
// tag::user_guide[]
@Test
@Timeout(5) // Poll at most 5 seconds
fun pollUntil() {
while (asynchronousResultNotAvailable()) {
Thread.sleep(250) // custom poll interval
}
// Obtain the asynchronous result and perform assertions
}
// end::user_guide[]
private fun asynchronousResultNotAvailable(): Boolean = false
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/RepeatedTestsDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
// tag::user_guide[]
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.RepeatedTest
import org.junit.jupiter.api.RepetitionInfo
import org.junit.jupiter.api.TestInfo
import org.junit.jupiter.api.fail
import org.junit.jupiter.api.parallel.Execution
import org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD
import java.util.logging.Logger
@Execution(SAME_THREAD)
class RepeatedTestsDemo {
private val logger = // ...
// end::user_guide[]
Logger.getLogger(RepeatedTestsDemo::class.java.name)
// tag::user_guide[]
@BeforeEach
fun beforeEach(
testInfo: TestInfo,
repetitionInfo: RepetitionInfo
) {
val currentRepetition = repetitionInfo.currentRepetition
val totalRepetitions = repetitionInfo.totalRepetitions
val methodName = testInfo.testMethod.get().name
logger.info("About to execute repetition $currentRepetition of $totalRepetitions for $methodName")
}
@RepeatedTest(10)
fun repeatedTest() {
// ...
}
@RepeatedTest(5)
fun repeatedTestWithRepetitionInfo(repetitionInfo: RepetitionInfo) {
assertEquals(5, repetitionInfo.totalRepetitions)
}
// end::user_guide[]
// Use fully qualified name to avoid having it show up in the imports.
@org.junit.jupiter.api.Disabled("intentional failures would break the build")
// tag::user_guide[]
@RepeatedTest(value = 8, failureThreshold = 2)
fun repeatedTestWithFailureThreshold(repetitionInfo: RepetitionInfo) {
// Simulate unexpected failure every second repetition
if (repetitionInfo.currentRepetition % 2 == 0) {
fail("Boom!")
}
}
@RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}")
@DisplayName("Repeat!")
fun customDisplayName(testInfo: TestInfo) {
assertEquals("Repeat! 1/1", testInfo.displayName)
}
@RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME)
@DisplayName("Details...")
fun customDisplayNameWithLongPattern(testInfo: TestInfo) {
assertEquals("Details... :: repetition 1 of 1", testInfo.displayName)
}
@RepeatedTest(value = 5, name = "Wiederholung {currentRepetition} von {totalRepetitions}")
fun repeatedTestInGerman() {
// ...
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/StandardTests.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
// tag::user_guide[]
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.fail
class StandardTests {
@BeforeEach
fun init() {
}
@Test
fun succeedingTest() {
}
// end::user_guide[]
@extensions.ExpectToFail
// tag::user_guide[]
@Test
fun failingTest() {
fail("a failing test")
}
@Test
@Disabled("for demonstration purposes")
fun skippedTest() {
// not executed
}
@Test
fun abortedTest() {
assumeTrue("abc".contains("Z"))
fail("test should have been aborted")
}
@AfterEach
fun tearDown() {
}
companion object {
@JvmStatic
@BeforeAll
fun initAll() {
}
@JvmStatic
@AfterAll
fun tearDownAll() {
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/SystemPropertyExtensionDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.ClassOrderer
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestClassOrder
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.util.ClearSystemProperty
import org.junit.jupiter.api.util.ReadsSystemProperty
import org.junit.jupiter.api.util.RestoreSystemProperties
import org.junit.jupiter.api.util.SetSystemProperty
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
class SystemPropertyExtensionDemo {
// tag::systemproperty_clear_simple[]
@Test
@ClearSystemProperty(key = "some property")
fun testClearingProperty() {
assertThat(System.getProperty("some property")).isNull()
}
// end::systemproperty_clear_simple[]
// tag::systemproperty_set_simple[]
@Test
@SetSystemProperty(key = "some property", value = "new value")
fun testSettingProperty() {
assertThat(System.getProperty("some property")).isEqualTo("new value")
}
// end::systemproperty_set_simple[]
// tag::systemproperty_using_set_and_clear[]
@Test
@ClearSystemProperty(key = "1st property")
@ClearSystemProperty(key = "2nd property")
@SetSystemProperty(key = "3rd property", value = "new value")
fun testClearingAndSettingProperty() {
assertThat(System.getProperty("1st property")).isNull()
assertThat(System.getProperty("2nd property")).isNull()
assertThat(System.getProperty("3rd property")).isEqualTo("new value")
}
// end::systemproperty_using_set_and_clear[]
// @formatter:off
@Nested
// tag::systemproperty_using_at_class_level[]
@ClearSystemProperty(key = "some property")
// end::systemproperty_using_at_class_level[]
inner
// tag::systemproperty_using_at_class_level[]
class MySystemPropertyTest {
// end::systemproperty_using_at_class_level[]
// @formatter:on
// tag::systemproperty_using_at_class_level[]
@Test
@SetSystemProperty(key = "some property", value = "new value")
fun clearedAtClasslevel() {
assertThat(System.getProperty("some property")).isEqualTo("new value")
}
}
// end::systemproperty_using_at_class_level[]
// tag::systemproperty_restore_test[]
@ParameterizedTest
@ValueSource(strings = ["foo", "bar"])
@RestoreSystemProperties
fun parameterizedTest(value: String) {
System.setProperty("some parameterized property", value)
System.setProperty("some other dynamic property", "my code calculates somehow")
}
// end::systemproperty_restore_test[]
@Nested
@TestClassOrder(ClassOrderer.OrderAnnotation::class)
inner class SystemPropertyRestoreExample {
// @formatter:off
@Nested
@Order(1)
// tag::systemproperty_class_restore_setup[]
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@RestoreSystemProperties
// end::systemproperty_class_restore_setup[]
inner
// tag::systemproperty_class_restore_setup[]
class MySystemPropertyRestoreTest {
// end::systemproperty_class_restore_setup[]
// @formatter:on
// tag::systemproperty_class_restore_setup[]
@BeforeAll
fun beforeAll() {
System.setProperty("A", "A value")
}
@BeforeEach
fun beforeEach() {
System.setProperty("B", "B value")
}
@Test
fun isolatedTest1() {
System.setProperty("C", "C value")
}
@Test
fun isolatedTest2() {
assertThat(System.getProperty("A")).isEqualTo("A value")
assertThat(System.getProperty("B")).isEqualTo("B value")
// Class-level @RestoreSystemProperties restores "C" to original state
assertThat(System.getProperty("C")).isNull()
}
}
// end::systemproperty_class_restore_setup[]
// @formatter:off
@Nested
@Order(2)
// tag::systemproperty_class_restore_isolated_class[]
@ReadsSystemProperty
// end::systemproperty_class_restore_isolated_class[]
inner
// tag::systemproperty_class_restore_isolated_class[]
class SomeOtherTestClass {
// end::systemproperty_class_restore_isolated_class[]
// @formatter:on
// tag::systemproperty_class_restore_isolated_class[]
@Test
fun isolatedTest() {
assertThat(System.getProperty("A")).isNull()
assertThat(System.getProperty("B")).isNull()
assertThat(System.getProperty("C")).isNull()
}
}
// end::systemproperty_class_restore_isolated_class[]
}
// tag::systemproperty_method_combine_all_test[]
@ParameterizedTest
@ValueSource(ints = [100, 500, 1000])
@RestoreSystemProperties
@SetSystemProperty(key = "DISABLE_CACHE", value = "TRUE")
@ClearSystemProperty(key = "COPYWRITE_OVERLAY_TEXT")
fun imageGenerationTest(imageSize: Int) {
System.setProperty("IMAGE_SIZE", "$imageSize") // Requires restore
// Test your image generation utility with the current system properties
}
// end::systemproperty_method_combine_all_test[]
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/TempDirectoryDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
import com.google.common.jimfs.Configuration
import com.google.common.jimfs.Jimfs
import example.kotlin.TempDirectoryDemo.InMemoryTempDirDemo.JimfsTempDirFactory
import example.util.ListWriter
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.AnnotatedElementContext
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS
import org.junit.jupiter.api.io.TempDir
import org.junit.jupiter.api.io.TempDirDeletionStrategy
import org.junit.jupiter.api.io.TempDirFactory
import java.nio.file.FileSystem
import java.nio.file.Files
import java.nio.file.Path
@Suppress("ClassName")
class TempDirectoryDemo {
// tag::user_guide_parameter_injection[]
@Test
fun writeItemsToFile(
@TempDir tempDir: Path
) {
val file = tempDir.resolve("test.txt")
ListWriter(file).write("a", "b", "c")
assertEquals(listOf("a,b,c"), Files.readAllLines(file))
}
// end::user_guide_parameter_injection[]
// tag::user_guide_multiple_directories[]
@Test
fun copyFileFromSourceToTarget(
@TempDir source: Path,
@TempDir target: Path
) {
val sourceFile = source.resolve("test.txt")
ListWriter(sourceFile).write("a", "b", "c")
val targetFile = Files.copy(sourceFile, target.resolve("test.txt"))
assertNotEquals(sourceFile, targetFile)
assertEquals(listOf("a,b,c"), Files.readAllLines(targetFile))
}
// end::user_guide_multiple_directories[]
// tag::user_guide_field_injection[]
class SharedTempDirectoryDemo {
@Test
fun writeItemsToFile() {
val file = sharedTempDir.resolve("test.txt")
ListWriter(file).write("a", "b", "c")
assertEquals(listOf("a,b,c"), Files.readAllLines(file))
}
@Test
fun anotherTestThatUsesTheSameTempDir() {
// use sharedTempDir
}
companion object {
@TempDir
@JvmStatic
lateinit var sharedTempDir: Path
}
}
// end::user_guide_field_injection[]
// tag::user_guide_cleanup_mode[]
class CleanupModeDemo {
@Test
fun fileTest(
@TempDir(cleanup = ON_SUCCESS) tempDir: Path
) {
// perform test
}
}
// end::user_guide_cleanup_mode[]
// tag::user_guide_factory_name_prefix[]
class TempDirFactoryDemo {
@Test
fun factoryTest(
@TempDir(factory = Factory::class) tempDir: Path
) {
assertTrue(tempDir.fileName.toString().startsWith("factoryTest"))
}
class Factory : TempDirFactory {
override fun createTempDirectory(
elementContext: AnnotatedElementContext,
extensionContext: ExtensionContext
): Path = Files.createTempDirectory(extensionContext.requiredTestMethod.name)
}
}
// end::user_guide_factory_name_prefix[]
// tag::user_guide_factory_jimfs[]
class InMemoryTempDirDemo {
@Test
fun test(
@TempDir(factory = JimfsTempDirFactory::class) tempDir: Path
) {
// perform test
}
class JimfsTempDirFactory : TempDirFactory {
private val fileSystem: FileSystem = Jimfs.newFileSystem(Configuration.unix())
override fun createTempDirectory(
elementContext: AnnotatedElementContext,
extensionContext: ExtensionContext
): Path = Files.createTempDirectory(fileSystem.getPath("/"), "junit-")
override fun close() {
fileSystem.close()
}
}
}
// end::user_guide_factory_jimfs[]
// tag::user_guide_deletion_strategy[]
class DeletionStrategyDemo {
@Test
fun test(
@TempDir(deletionStrategy = TempDirDeletionStrategy.IgnoreFailures::class)
tempDir: Path
) {
// perform test
}
}
// end::user_guide_deletion_strategy[]
// tag::user_guide_composed_annotation[]
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@TempDir(factory = JimfsTempDirFactory::class)
annotation class JimfsTempDir
// end::user_guide_composed_annotation[]
// tag::user_guide_composed_annotation_usage[]
class JimfsTempDirAnnotationDemo {
@Test
fun test(
@JimfsTempDir tempDir: Path
) {
// perform test
}
}
// end::user_guide_composed_annotation_usage[]
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/TestInfoDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
// tag::user_guide[]
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInfo
@DisplayName("TestInfo Demo")
class TestInfoDemo(
testInfo: TestInfo
) {
companion object {
@JvmStatic
@BeforeAll
fun beforeAll(testInfo: TestInfo) {
assertEquals("TestInfo Demo", testInfo.displayName)
}
}
init {
assertTrue(testInfo.displayName in listOf("TEST 1", "test2()"))
}
@BeforeEach
fun init(testInfo: TestInfo) {
assertTrue(testInfo.displayName in listOf("TEST 1", "test2()"))
}
@Test
@DisplayName("TEST 1")
@Tag("my-tag")
fun test1(testInfo: TestInfo) {
assertEquals("TEST 1", testInfo.displayName)
assertTrue("my-tag" in testInfo.tags)
}
@Test
fun test2() {
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/TestReporterDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
import org.junit.jupiter.api.MediaType
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestReporter
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Files
import java.nio.file.Path
// tag::user_guide[]
class TestReporterDemo {
@Test
fun reportSingleValue(testReporter: TestReporter) {
testReporter.publishEntry("a status message")
}
@Test
fun reportKeyValuePair(testReporter: TestReporter) {
testReporter.publishEntry("a key", "a value")
}
@Test
fun reportMultipleKeyValuePairs(testReporter: TestReporter) {
testReporter.publishEntry(
mapOf(
"user name" to "dk38",
"award year" to "1974"
)
)
}
@Test
fun reportFiles(
testReporter: TestReporter,
@TempDir tempDir: Path
) {
testReporter.publishFile("test1.txt", MediaType.TEXT_PLAIN_UTF_8) { file ->
Files.write(file, listOf("Test 1"))
}
val existingFile = Files.write(tempDir.resolve("test2.txt"), listOf("Test 2"))
testReporter.publishFile(existingFile, MediaType.TEXT_PLAIN_UTF_8)
testReporter.publishDirectory("test3") { dir ->
Files.write(dir.resolve("nested1.txt"), listOf("Nested content 1"))
Files.write(dir.resolve("nested2.txt"), listOf("Nested content 2"))
}
val existingDir = Files.createDirectory(tempDir.resolve("test4"))
Files.write(existingDir.resolve("nested1.txt"), listOf("Nested content 1"))
Files.write(existingDir.resolve("nested2.txt"), listOf("Nested content 2"))
testReporter.publishDirectory(existingDir)
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/TestingAStackDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
// tag::user_guide[]
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.util.EmptyStackException
import java.util.Stack
@DisplayName("A stack")
class TestingAStackDemo {
@Test
@DisplayName("is instantiated with new Stack()")
fun isInstantiatedWithNew() {
Stack()
}
@Nested
@DisplayName("when new")
inner class WhenNew {
lateinit var stack: Stack
@BeforeEach
fun createNewStack() {
stack = Stack()
}
@Test
@DisplayName("is empty")
fun isEmpty() {
assertTrue(stack.isEmpty())
}
@Test
@DisplayName("throws EmptyStackException when popped")
fun throwsExceptionWhenPopped() {
assertThrows { stack.pop() }
}
@Test
@DisplayName("throws EmptyStackException when peeked")
fun throwsExceptionWhenPeeked() {
assertThrows { stack.peek() }
}
@Nested
@DisplayName("after pushing an element")
inner class AfterPushing {
val anElement = "an element"
@BeforeEach
fun pushAnElement() {
stack.push(anElement)
}
@Test
@DisplayName("it is no longer empty")
fun isNotEmpty() {
assertFalse(stack.isEmpty())
}
@Test
@DisplayName("returns the element when popped and is empty")
fun returnElementWhenPopped() {
assertEquals(anElement, stack.pop())
assertTrue(stack.isEmpty())
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
fun returnElementWhenPeeked() {
assertEquals(anElement, stack.peek())
assertFalse(stack.isEmpty())
}
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/TimeoutDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Timeout
import org.junit.jupiter.api.Timeout.ThreadMode
import java.util.concurrent.TimeUnit
// tag::user_guide[]
@Tag("timeout")
class TimeoutDemo {
@BeforeEach
@Timeout(5)
fun setUp() {
// fails if execution time exceeds 5 seconds
}
@Test
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
fun failsIfExecutionTimeExceeds500Milliseconds() {
// fails if execution time exceeds 500 milliseconds
}
@Test
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS, threadMode = ThreadMode.SEPARATE_THREAD)
fun failsIfExecutionTimeExceeds500MillisecondsInSeparateThread() {
// fails if execution time exceeds 500 milliseconds, the test code is executed in a separate thread
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/defaultmethods/ComparableContract.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.defaultmethods
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
// tag::user_guide[]
interface ComparableContract> : Testable {
fun createSmallerValue(): T
@Test
fun returnsZeroWhenComparedToItself() {
val value = createValue()
assertEquals(0, value.compareTo(value))
}
@Test
fun returnsPositiveNumberWhenComparedToSmallerValue() {
val value = createValue()
val smallerValue = createSmallerValue()
assertTrue(value > smallerValue)
}
@Test
fun returnsNegativeNumberWhenComparedToLargerValue() {
val value = createValue()
val smallerValue = createSmallerValue()
assertTrue(smallerValue < value)
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/defaultmethods/EqualsContract.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.defaultmethods
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
import org.junit.jupiter.api.Test
// tag::user_guide[]
interface EqualsContract : Testable {
fun createNotEqualValue(): T
@Test
fun valueEqualsItself() {
val value = createValue()
assertEquals(value, value)
}
@Test
fun valueDoesNotEqualNull() {
val value = createValue()
assertNotEquals(null, value)
}
@Test
fun valueDoesNotEqualDifferentValue() {
val value = createValue()
val differentValue = createNotEqualValue()
assertNotEquals(value, differentValue)
assertNotEquals(differentValue, value)
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/defaultmethods/StringTests.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.defaultmethods
// tag::user_guide[]
class StringTests :
ComparableContract,
EqualsContract {
override fun createValue() = "banana"
override fun createSmallerValue() = "apple" // 'a' < 'b' in "banana"
override fun createNotEqualValue() = "cherry"
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/defaultmethods/Testable.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.defaultmethods
// tag::user_guide[]
interface Testable {
fun createValue(): T
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/exception/AssertDoesNotThrowExceptionDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.exception
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
class AssertDoesNotThrowExceptionDemo {
// tag::user_guide[]
@Test
fun testExceptionIsNotThrown() {
assertDoesNotThrow {
shouldNotThrowException()
}
}
fun shouldNotThrowException() {
}
// end::user_guide[]
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/exception/ExceptionAssertionDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.exception
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
class ExceptionAssertionDemo {
// tag::user_guide[]
@Test
fun testExpectedExceptionIsThrown() {
// The following assertion succeeds because the code under assertion
// throws the expected IllegalArgumentException.
// The assertion also returns the thrown exception which can be used for
// further assertions like asserting the exception message.
val exception =
assertThrows {
throw IllegalArgumentException("expected message")
}
assertEquals("expected message", exception.message)
// The following assertion also succeeds because the code under assertion
// throws IllegalArgumentException which is a subclass of RuntimeException.
assertThrows {
throw IllegalArgumentException("expected message")
}
}
// end::user_guide[]
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/exception/ExceptionAssertionExactDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.exception
import extensions.ExpectToFail
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrowsExactly
class ExceptionAssertionExactDemo {
@ExpectToFail
// tag::user_guide[]
@Test
fun testExpectedExceptionIsThrown() {
// The following assertion succeeds because the code under assertion throws
// IllegalArgumentException which is exactly equal to the expected type.
// The assertion also returns the thrown exception which can be used for
// further assertions like asserting the exception message.
val exception =
assertThrowsExactly {
throw IllegalArgumentException("expected message")
}
assertEquals("expected message", exception.message)
// The following assertion fails because the assertion expects exactly
// RuntimeException to be thrown, not subclasses of RuntimeException.
assertThrowsExactly {
throw IllegalArgumentException("expected message")
}
}
// end::user_guide[]
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/exception/FailedAssertionDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.exception
import example.util.Calculator
import extensions.ExpectToFail
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class FailedAssertionDemo {
// tag::user_guide[]
private val calculator = Calculator()
// end::user_guide[]
@ExpectToFail
// tag::user_guide[]
@Test
fun failsDueToUncaughtAssertionError() {
// The following incorrect assertion will cause a test failure.
// The expected value should be 2 instead of 99.
assertEquals(99, calculator.add(1, 1))
}
// end::user_guide[]
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/exception/UncaughtExceptionHandlingDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.exception
import example.util.Calculator
import extensions.ExpectToFail
import org.junit.jupiter.api.Test
class UncaughtExceptionHandlingDemo {
// tag::user_guide[]
private val calculator = Calculator()
// end::user_guide[]
@ExpectToFail
// tag::user_guide[]
@Test
fun failsDueToUncaughtException() {
// The following throws an ArithmeticException due to division by
// zero, which causes a test failure.
calculator.divide(1, 0)
}
// end::user_guide[]
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/extensions/HttpServerExtension.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.extensions
import com.sun.net.httpserver.HttpServer
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ExtensionContext.Namespace
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
import java.io.IOException
import java.io.UncheckedIOException
// tag::user_guide[]
class HttpServerExtension : ParameterResolver {
override fun supportsParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Boolean = HttpServer::class.java == parameterContext.parameter.type
override fun resolveParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Any {
val rootContext = extensionContext.root
val store = rootContext.getStore(Namespace.GLOBAL)
val key = HttpServerResource::class.java
val resource =
store.computeIfAbsent(key, {
try {
HttpServerResource(0).apply { start() }
} catch (e: IOException) {
throw UncheckedIOException("Failed to create HttpServerResource", e)
}
}, HttpServerResource::class.java)
return resource.httpServer
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/extensions/HttpServerResource.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.extensions
import com.sun.net.httpserver.HttpServer
import java.net.InetAddress
import java.net.InetSocketAddress
// tag::user_guide[]
class HttpServerResource(
port: Int
) : AutoCloseable {
val httpServer: HttpServer
init {
val loopbackAddress = InetAddress.getLoopbackAddress()
httpServer = HttpServer.create(InetSocketAddress(loopbackAddress, port), 0)
}
// end::user_guide[]
// tag::user_guide[]
fun start() {
// Example handler
httpServer.createContext("/example") { exchange ->
val body = "This is a test"
exchange.sendResponseHeaders(200, body.length.toLong())
exchange.responseBody.use { os ->
os.write(body.toByteArray(Charsets.UTF_8))
}
}
httpServer.executor = null
httpServer.start()
}
override fun close() {
httpServer.stop(0)
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverConflictDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.extensions
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
// tag::user_guide[]
class ParameterResolverConflictDemo {
// end::user_guide[]
@extensions.ExpectToFail
// tag::user_guide[]
@Test
@ExtendWith(FirstIntegerResolver::class, SecondIntegerResolver::class)
fun testInt(i: Int) {
// Test will not run due to ParameterResolutionException
assertEquals(1, i)
}
class FirstIntegerResolver : ParameterResolver {
override fun supportsParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Boolean = parameterContext.parameter.type == Int::class.javaPrimitiveType
override fun resolveParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Any = 1
}
class SecondIntegerResolver : ParameterResolver {
override fun supportsParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Boolean = parameterContext.parameter.type == Int::class.javaPrimitiveType
override fun resolveParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Any = 2
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverCustomAnnotationDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.extensions
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
// tag::user_guide[]
class ParameterResolverCustomAnnotationDemo {
@Test
fun testInt(
@FirstInteger first: Int,
@SecondInteger second: Int
) {
assertEquals(1, first)
assertEquals(2, second)
}
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(FirstIntegerExtension::class)
annotation class FirstInteger
class FirstIntegerExtension : ParameterResolver {
override fun supportsParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Boolean =
parameterContext.parameter.type == Int::class.javaPrimitiveType &&
!parameterContext.isAnnotated(SecondInteger::class.java)
override fun resolveParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Any = 1
}
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(SecondIntegerExtension::class)
annotation class SecondInteger
class SecondIntegerExtension : ParameterResolver {
override fun supportsParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Boolean = parameterContext.isAnnotated(SecondInteger::class.java)
override fun resolveParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Any = 2
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverCustomTypeDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.extensions
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
// tag::user_guide[]
class ParameterResolverCustomTypeDemo {
@Test
@ExtendWith(FirstIntegerResolver::class, SecondIntegerResolver::class)
fun testInt(
i: Int,
wrappedInteger: WrappedInteger
) {
assertEquals(1, i)
assertEquals(2, wrappedInteger.value)
}
class FirstIntegerResolver : ParameterResolver {
override fun supportsParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Boolean = parameterContext.parameter.type == Int::class.javaPrimitiveType
override fun resolveParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Any = 1
}
class SecondIntegerResolver : ParameterResolver {
override fun supportsParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Boolean = parameterContext.parameter.type == WrappedInteger::class.java
override fun resolveParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Any = WrappedInteger(2)
}
data class WrappedInteger(
val value: Int
)
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverNoConflictDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.extensions
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
// tag::user_guide[]
class ParameterResolverNoConflictDemo {
@Test
@ExtendWith(FirstIntegerResolver::class)
fun firstResolution(i: Int) {
assertEquals(1, i)
}
@Test
@ExtendWith(SecondIntegerResolver::class)
fun secondResolution(i: Int) {
assertEquals(2, i)
}
class FirstIntegerResolver : ParameterResolver {
override fun supportsParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Boolean = parameterContext.parameter.type == Int::class.javaPrimitiveType
override fun resolveParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Any = 1
}
class SecondIntegerResolver : ParameterResolver {
override fun supportsParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Boolean = parameterContext.parameter.type == Int::class.javaPrimitiveType
override fun resolveParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext
): Any = 2
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/extensions/Random.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.extensions
import org.junit.jupiter.api.extension.ExtendWith
// tag::user_guide[]
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(RandomNumberExtension::class)
annotation class Random
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/extensions/RandomNumberDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.extensions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
// tag::user_guide[]
class RandomNumberDemo(
@Random randomNumber2: Int
) {
// Use randomNumber1 field in test methods and @BeforeEach
// or @AfterEach lifecycle methods.
@Random
var randomNumber1: Int = 0
// randomNumber2 is available in the constructor via primary constructor above.
@BeforeEach
fun beforeEach(
@Random randomNumber3: Int
) {
// Use randomNumber3 in @BeforeEach method.
}
@Test
fun test(
@Random randomNumber4: Int
) {
// Use randomNumber4 in test method.
}
companion object {
// Use static randomNumber0 field anywhere in the test class,
// including @BeforeAll or @AfterAll lifecycle methods.
@Random
@JvmStatic
var randomNumber0: Int = 0
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/extensions/RandomNumberExtension.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.extensions
// tag::user_guide[]
import org.junit.jupiter.api.extension.BeforeAllCallback
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
import org.junit.jupiter.api.extension.TestInstancePostProcessor
import org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields
import org.junit.platform.commons.support.ModifierSupport
import java.lang.reflect.Field
import java.util.function.Predicate
class RandomNumberExtension :
BeforeAllCallback,
TestInstancePostProcessor,
ParameterResolver {
private val random = java.util.Random(System.nanoTime())
/**
* Inject a random integer into static fields that are annotated with
* `@Random` and can be assigned an integer value.
*/
override fun beforeAll(context: ExtensionContext) {
val testClass = context.requiredTestClass
injectFields(testClass, null, ModifierSupport::isStatic)
}
/**
* Inject a random integer into non-static fields that are annotated with
* `@Random` and can be assigned an integer value.
*/
override fun postProcessTestInstance(
testInstance: Any,
context: ExtensionContext
) {
val testClass = context.requiredTestClass
injectFields(testClass, testInstance, ModifierSupport::isNotStatic)
}
/**
* Determine if the parameter is annotated with `@Random` and can be
* assigned an integer value.
*/
override fun supportsParameter(
pc: ParameterContext,
ec: ExtensionContext
): Boolean = pc.isAnnotated(Random::class.java) && isInteger(pc.parameter.type)
/**
* Resolve a random integer.
*/
override fun resolveParameter(
pc: ParameterContext,
ec: ExtensionContext
): Int = random.nextInt()
private fun injectFields(
testClass: Class<*>,
testInstance: Any?,
predicate: Predicate
) {
val combined = predicate.and { field -> isInteger(field.type) }
findAnnotatedFields(testClass, Random::class.java, combined)
.forEach { field ->
field.isAccessible = true
field.set(testInstance, random.nextInt())
}
}
private fun isInteger(type: Class<*>): Boolean = type == Int::class.javaObjectType || type == Int::class.javaPrimitiveType
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/registration/DocumentationDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.registration
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
import java.nio.file.Path
// tag::user_guide[]
class DocumentationDemo {
@JvmField
@RegisterExtension
val docs: DocumentationExtension =
DocumentationExtension.forPath(lookUpDocsDir())
@Test
fun generateDocumentation() {
// use this.docs ...
}
companion object {
fun lookUpDocsDir(): Path? {
// return path to docs dir
return null
}
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/registration/DocumentationExtension.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.registration
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
import java.nio.file.Path
class DocumentationExtension private constructor(
private val path: Path?
) : AfterEachCallback {
override fun afterEach(context: ExtensionContext) {
// no-op for demo
}
companion object {
@JvmStatic
fun forPath(path: Path?): DocumentationExtension = DocumentationExtension(path)
}
}
================================================
FILE: documentation/src/test/kotlin/example/kotlin/testinterface/TestInterfaceDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.testinterface
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
// tag::user_guide[]
class TestInterfaceDemo :
TestLifecycleLogger,
TimeExecutionLogger,
TestInterfaceDynamicTestsDemo {
@Test
fun isEqualValue() {
assertEquals(1, "a".length, "is always equal")
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/testinterface/TestInterfaceDynamicTestsDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.testinterface
import example.util.StringUtils.isPalindrome
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.DynamicTest.dynamicTest
import org.junit.jupiter.api.TestFactory
// tag::user_guide[]
interface TestInterfaceDynamicTestsDemo {
@TestFactory
fun dynamicTestsForPalindromes(): Sequence =
sequenceOf("racecar", "radar", "mom", "dad")
.map { text -> dynamicTest(text) { assertTrue(isPalindrome(text)) } }
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/testinterface/TestLifecycleLogger.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.testinterface
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.TestInfo
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle
import java.util.logging.Logger
// tag::user_guide[]
@TestInstance(Lifecycle.PER_CLASS)
interface TestLifecycleLogger {
@BeforeAll
fun beforeAllTests() {
logger.info("Before all tests")
}
@AfterAll
fun afterAllTests() {
logger.info("After all tests")
}
@BeforeEach
fun beforeEachTest(testInfo: TestInfo) {
logger.info { "About to execute [${testInfo.displayName}]" }
}
@AfterEach
fun afterEachTest(testInfo: TestInfo) {
logger.info { "Finished executing [${testInfo.displayName}]" }
}
companion object {
private val logger: Logger = Logger.getLogger(TestLifecycleLogger::class.java.name)
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/testinterface/TimeExecutionLogger.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.testinterface
import example.timing.TimingExtension
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.extension.ExtendWith
// tag::user_guide[]
@Tag("timed")
@ExtendWith(TimingExtension::class)
interface TimeExecutionLogger
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/timing/TimingExtension.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.timing
// tag::user_guide[]
import org.junit.jupiter.api.extension.AfterTestExecutionCallback
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ExtensionContext.Namespace
import org.junit.jupiter.api.extension.ExtensionContext.Store
import java.util.logging.Logger
// end::user_guide[]
// tag::user_guide[]
class TimingExtension :
BeforeTestExecutionCallback,
AfterTestExecutionCallback {
override fun beforeTestExecution(context: ExtensionContext) {
getStore(context).put(START_TIME, System.currentTimeMillis())
}
override fun afterTestExecution(context: ExtensionContext) {
val testMethod = context.requiredTestMethod
val startTime = getStore(context).remove(START_TIME, Long::class.javaObjectType)!!
val duration = System.currentTimeMillis() - startTime
logger.info { "Method [${testMethod.name}] took $duration ms." }
}
private fun getStore(context: ExtensionContext): Store = context.getStore(Namespace.create(javaClass, context.requiredTestMethod))
companion object {
private val logger: Logger = Logger.getLogger(TimingExtension::class.java.name)
private const val START_TIME = "start time"
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/kotlin/timing/TimingExtensionTests.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.kotlin.timing
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
// tag::user_guide[]
@ExtendWith(TimingExtension::class)
class TimingExtensionTests {
@Test
fun sleep20ms() {
Thread.sleep(20)
}
@Test
fun sleep50ms() {
Thread.sleep(50)
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package example.registration
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
// tag::user_guide[]
class KotlinWebServerDemo {
companion object {
@JvmField
@RegisterExtension
val server =
WebServerExtension
.builder()
.enableSecurity(false)
.build()!!
}
@Test
fun getProductList() {
// Use WebClient to connect to web server using serverUrl and verify response
val webClient = WebClient()
val serverUrl = server.serverUrl
assertEquals(200, webClient.get("$serverUrl/products").responseStatus)
}
}
// end::user_guide[]
================================================
FILE: documentation/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener
================================================
example.session.GlobalSetupTeardownListener
================================================
FILE: documentation/src/test/resources/junit-platform.properties
================================================
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.executor-service=worker_thread_pool
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=6
junit.platform.stacktrace.pruning.enabled=false
================================================
FILE: documentation/src/test/resources/log4j2-test.xml
================================================
================================================
FILE: documentation/src/test/resources/two-column.csv
================================================
COUNTRY, REFERENCE
Sweden, 1
Poland, 2
"United States of America", 3
France, 700_000
================================================
FILE: documentation/src/tools/java/org/junit/api/tools/AbstractApiReportWriter.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package org.junit.api.tools;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apiguardian.api.API.Status;
/**
* @since 1.0
*/
abstract class AbstractApiReportWriter implements ApiReportWriter {
protected static final int NAME_COLUMN_WIDTH = 128;
private final ApiReport apiReport;
AbstractApiReportWriter(ApiReport apiReport) {
this.apiReport = apiReport;
}
@Override
public void printReportHeader(PrintWriter out) {
out.println(h1("@API Declarations"));
out.println();
out.println(paragraph(
"Discovered %d types with %s declarations.".formatted(this.apiReport.types().size(), code("@API"))));
out.println();
}
@Override
public void printDeclarationInfo(PrintWriter out, Set statuses) {
statuses.forEach(
status -> printDeclarationSection(statuses, status, this.apiReport.declarations().get(status), out));
}
protected void printDeclarationSection(Set statuses, Status status, List declarations,
PrintWriter out) {
printDeclarationSectionHeader(statuses, status, declarations, out);
Map> declarationsByModule = declarations.stream() //
.collect(groupingBy(Declaration::moduleName, TreeMap::new, toList()));
if (declarationsByModule.isEmpty()) {
out.println(paragraph("NOTE: There are currently no APIs annotated with %s.".formatted(
code("@API(status = %s)".formatted(status.name())))));
return;
}
declarationsByModule.forEach((moduleName, moduleDeclarations) -> {
out.println(h3("Module " + moduleName));
out.println();
moduleDeclarations.stream() //
.collect(groupingBy(Declaration::packageName, TreeMap::new, toList())) //
.forEach((packageName, packageDeclarations) -> {
out.println(h4("Package " + packageName));
out.println();
printDeclarationTableHeader(out);
packageDeclarations.forEach(it -> printDeclarationTableRow(it, out));
printDeclarationTableFooter(out);
out.println();
});
});
}
protected void printDeclarationSectionHeader(Set statuses, Status status, List declarations,
PrintWriter out) {
if (statuses.size() < 2) {
// omit section header when only a single status is printed
return;
}
out.println(h2("@API(%s)".formatted(status)));
out.println();
out.println(paragraph(
"Discovered %d %s declarations.".formatted(declarations.size(), code("@API(%s)".formatted(status)))));
out.println();
}
protected abstract String h1(String header);
protected abstract String h2(String header);
protected abstract String h3(String header);
protected abstract String h4(String header);
protected abstract String code(String element);
protected abstract String italic(String element);
protected String paragraph(String element) {
return element;
}
protected abstract void printDeclarationTableHeader(PrintWriter out);
protected abstract void printDeclarationTableRow(Declaration declaration, PrintWriter out);
protected abstract void printDeclarationTableFooter(PrintWriter out);
}
================================================
FILE: documentation/src/tools/java/org/junit/api/tools/ApiReport.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package org.junit.api.tools;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import io.github.classgraph.ClassInfo;
import org.apiguardian.api.API.Status;
/**
* @since 1.0
*/
record ApiReport(SortedSet types, Map> declarations) {
}
================================================
FILE: documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package org.junit.api.tools;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toUnmodifiableSet;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.lang.module.ModuleFinder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Stream;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.MethodInfo;
import io.github.classgraph.ScanResult;
import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
/**
* @since 1.0
*/
class ApiReportGenerator {
private static final Logger logger = LoggerFactory.getLogger(ApiReportGenerator.class);
private static final String EOL = System.lineSeparator();
public static void main(String... args) {
// CAUTION: The output produced by this method is used to
// generate a table in the User Guide.
try (var scanResult = scanClasspath()) {
var apiReport = generateReport(scanResult);
// ApiReportWriter reportWriter = new MarkdownApiReportWriter(apiReport);
ApiReportWriter reportWriter = new AsciidocApiReportWriter(apiReport);
// ApiReportWriter reportWriter = new HtmlApiReportWriter(apiReport);
// reportWriter.printReportHeader(new PrintWriter(System.out, true));
// Print report for all Usage enum constants
// reportWriter.printDeclarationInfo(new PrintWriter(System.out, true), EnumSet.allOf(Status.class));
// Print report only for specific Status constants, defaults to only EXPERIMENTAL
parseArgs(args).forEach((status, opener) -> {
try (var stream = opener.openStream()) {
var writer = new PrintWriter(stream == null ? System.out : stream, true, UTF_8);
reportWriter.printDeclarationInfo(writer, EnumSet.of(status));
}
catch (IOException e) {
throw new UncheckedIOException("Failed to write report", e);
}
});
}
}
// -------------------------------------------------------------------------
private static Map parseArgs(String[] args) {
Map outputByStatus = new EnumMap<>(Status.class);
if (args.length == 0) {
outputByStatus.put(Status.EXPERIMENTAL, () -> null);
}
else {
Arrays.stream(args) //
.map(arg -> arg.split("=", 2)) //
.forEach(parts -> outputByStatus.put(//
Status.valueOf(parts[0]), //
() -> parts.length < 2 //
? null //
: new BufferedOutputStream(Files.newOutputStream(Path.of(parts[1]))) //
));
}
return outputByStatus;
}
private interface StreamOpener {
OutputStream openStream() throws IOException;
}
private static ApiReport generateReport(ScanResult scanResult) {
Map> declarations = new EnumMap<>(Status.class);
for (var status : Status.values()) {
declarations.put(status, new ArrayList<>());
}
var types = collectTypes(scanResult);
types.stream() //
.map(Declaration.Type::new) //
.forEach(type -> declarations.get(type.status()).add(type));
collectMethods(scanResult) //
.map(Declaration.Method::new) //
.filter(method -> !declarations.get(method.status()) //
.contains(new Declaration.Type(method.classInfo()))) //
.forEach(method -> {
types.add(method.classInfo());
declarations.get(method.status()).add(method);
});
declarations.values().forEach(list -> list.sort(null));
return new ApiReport(types, declarations);
}
private static ScanResult scanClasspath() {
// scan all types below "org.junit" package
var classGraph = new ClassGraph() //
.acceptPackages("org.junit") //
.rejectPackages("*.shadow.*", "org.opentest4j.*", "org.junit.platform.commons.logging",
"org.junit.platform.commons.util") //
.disableNestedJarScanning() //
.enableClassInfo() //
.enableMethodInfo() //
.enableAnnotationInfo(); //
var apiClasspath = System.getProperty("api.modulePath");
var apiModules = System.getProperty("api.moduleNames");
if (apiClasspath != null && apiModules != null) {
var paths = Arrays.stream(apiClasspath.split(File.pathSeparator)).map(Path::of).toArray(Path[]::new);
var bootLayer = ModuleLayer.boot();
var roots = Arrays.stream(apiModules.split(",")).collect(toUnmodifiableSet());
var configuration = bootLayer.configuration().resolveAndBind(ModuleFinder.of(), ModuleFinder.of(paths),
roots);
var layer = bootLayer.defineModulesWithOneLoader(configuration, ClassLoader.getPlatformClassLoader());
classGraph = classGraph.overrideModuleLayers(layer);
}
return classGraph.scan();
}
private static SortedSet collectTypes(ScanResult scanResult) {
var types = scanResult.getClassesWithAnnotation(API.class).stream() //
.filter(it -> !it.getAnnotationInfo(API.class).isInherited()) //
.collect(toCollection(TreeSet::new));
logger.debug(() -> {
var builder = new StringBuilder("Listing of all " + types.size() + " annotated types:");
builder.append(EOL);
types.forEach(e -> builder.append(e.getName()).append(EOL));
return builder.toString();
});
return types;
}
private static Stream collectMethods(ScanResult scanResult) {
return scanResult.getClassesWithMethodAnnotation(API.class).stream() //
.flatMap(type -> type.getDeclaredMethodAndConstructorInfo().stream()) //
.filter(m -> m.getAnnotationInfo(API.class) != null);
}
private ApiReportGenerator() {
}
}
================================================
FILE: documentation/src/tools/java/org/junit/api/tools/ApiReportWriter.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package org.junit.api.tools;
import java.io.PrintWriter;
import java.util.Set;
import org.apiguardian.api.API.Status;
/**
* @since 1.0
*/
interface ApiReportWriter {
void printReportHeader(PrintWriter out);
void printDeclarationInfo(PrintWriter out, Set statuses);
}
================================================
FILE: documentation/src/tools/java/org/junit/api/tools/AsciidocApiReportWriter.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package org.junit.api.tools;
import java.io.PrintWriter;
/**
* @since 1.0
*/
class AsciidocApiReportWriter extends AbstractApiReportWriter {
private static final String ASCIIDOC_FORMAT = "|%-" + NAME_COLUMN_WIDTH + "s | %-12s%n";
AsciidocApiReportWriter(ApiReport apiReport) {
super(apiReport);
}
@Override
protected String h1(String header) {
return "= " + header;
}
@Override
protected String h2(String header) {
return "== " + header;
}
@Override
protected String h3(String header) {
return "%n=== %s".formatted(header);
}
@Override
protected String h4(String header) {
return "%n==== %s".formatted(header);
}
@Override
protected String code(String element) {
return "`" + element + "`";
}
@Override
protected String italic(String element) {
return "_" + element + "_";
}
@Override
protected void printDeclarationTableHeader(PrintWriter out) {
out.println("[cols=\"99,1\"]");
out.println("|===");
out.printf(ASCIIDOC_FORMAT, "Name", "Since");
out.println();
}
@Override
protected void printDeclarationTableRow(Declaration declaration, PrintWriter out) {
out.printf(ASCIIDOC_FORMAT, //
code(declaration.name().replace(".", ".​")) + " " + italic("(" + declaration.kind() + ")"), //
code(declaration.since()) //
);
}
@Override
protected void printDeclarationTableFooter(PrintWriter out) {
out.println("|===");
}
}
================================================
FILE: documentation/src/tools/java/org/junit/api/tools/Declaration.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package org.junit.api.tools;
import static java.util.stream.Collectors.joining;
import java.util.Arrays;
import io.github.classgraph.AnnotationEnumValue;
import io.github.classgraph.AnnotationParameterValueList;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.MethodInfo;
import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;
sealed interface Declaration extends Comparable {
String moduleName();
String packageName();
String fullName();
String name();
String kind();
Status status();
String since();
@Override
default int compareTo(Declaration o) {
return fullName().compareTo(o.fullName());
}
record Type(ClassInfo classInfo) implements Declaration {
@Override
public String moduleName() {
return classInfo.getModuleRef().getName();
}
@Override
public String packageName() {
return classInfo.getPackageName();
}
@Override
public String fullName() {
return classInfo.getName();
}
@Override
public String name() {
var shortClassName = getShortClassName(classInfo);
return classInfo.isAnnotation() ? "@" + shortClassName : shortClassName;
}
@Override
public String kind() {
return switch (classInfo) {
case ClassInfo ignored when classInfo.isRecord() -> "record";
case ClassInfo ignored when classInfo.isAnnotation() -> "annotation";
case ClassInfo ignored when classInfo.isEnum() -> "enum";
case ClassInfo ignored when classInfo.isInterface() -> "interface";
default -> "class";
};
}
@Override
public Status status() {
return readStatus(getParameterValues());
}
@Override
public String since() {
return readSince(getParameterValues());
}
private AnnotationParameterValueList getParameterValues() {
return classInfo.getAnnotationInfo(API.class).getParameterValues();
}
}
record Method(MethodInfo methodInfo) implements Declaration {
@Override
public String moduleName() {
return classInfo().getModuleRef().getName();
}
@Override
public String packageName() {
return classInfo().getPackageName();
}
@Override
public String fullName() {
return "%s.%s".formatted(classInfo().getName(), methodSignature());
}
@Override
public String name() {
if (classInfo().isAnnotation()) {
return "@%s(%s=...)".formatted(getShortClassName(classInfo()), methodInfo.getName());
}
if (methodInfo.isConstructor()) {
return "%s%s".formatted(getShortClassName(classInfo()), methodParameters());
}
return "%s.%s".formatted(getShortClassName(classInfo()), methodSignature());
}
private String methodSignature() {
return methodInfo.getName() + methodParameters();
}
private String methodParameters() {
return Arrays.stream(methodInfo.getParameterInfo()) //
.map(parameterInfo -> parameterInfo.getTypeSignatureOrTypeDescriptor().toStringWithSimpleNames()) //
.collect(joining(", ", "(", ")"));
}
@Override
public String kind() {
if (methodInfo.isConstructor()) {
return "constructor";
}
if (classInfo().isAnnotation()) {
return "annotation attribute";
}
return "method";
}
@Override
public Status status() {
return readStatus(getParameterValues());
}
@Override
public String since() {
return readSince(getParameterValues());
}
private AnnotationParameterValueList getParameterValues() {
return methodInfo.getAnnotationInfo(API.class).getParameterValues();
}
public ClassInfo classInfo() {
return methodInfo.getClassInfo();
}
}
private static Status readStatus(AnnotationParameterValueList parameterValues) {
return Status.valueOf(((AnnotationEnumValue) parameterValues.getValue("status")).getValueName());
}
private static String readSince(AnnotationParameterValueList parameterValues) {
return (String) parameterValues.getValue("since");
}
private static String getShortClassName(ClassInfo classInfo) {
var typeName = classInfo.getName();
var packageName = classInfo.getPackageName();
if (typeName.startsWith(packageName + '.')) {
typeName = typeName.substring(packageName.length() + 1);
}
return typeName.replace('$', '.');
}
}
================================================
FILE: documentation/src/tools/java/org/junit/api/tools/HtmlApiReportWriter.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package org.junit.api.tools;
import java.io.PrintWriter;
/**
* @since 1.0
*/
class HtmlApiReportWriter extends AbstractApiReportWriter {
private static final String HTML_HEADER_FORMAT = "\t%s %s %n";
private static final String HTML_ROW_FORMAT = "\t%s %s %n";
HtmlApiReportWriter(ApiReport apiReport) {
super(apiReport);
}
@Override
protected String h1(String header) {
return "" + header + " ";
}
@Override
protected String h2(String header) {
return "" + header + " ";
}
@Override
protected String h3(String header) {
return "" + header + " ";
}
@Override
protected String h4(String header) {
return "" + header + " ";
}
@Override
protected String code(String element) {
return "" + element + " ";
}
@Override
protected String italic(String element) {
return "" + element + " ";
}
@Override
protected String paragraph(String element) {
return "" + element + "
";
}
@Override
protected void printDeclarationTableHeader(PrintWriter out) {
out.println("");
out.printf(HTML_HEADER_FORMAT, "Name", "Since");
}
@Override
protected void printDeclarationTableRow(Declaration declaration, PrintWriter out) {
out.printf(HTML_ROW_FORMAT, //
code(declaration.name()) + " " + italic("(" + declaration.kind() + ")"), //
code(declaration.since()) //
);
}
@Override
protected void printDeclarationTableFooter(PrintWriter out) {
out.println("
");
}
}
================================================
FILE: documentation/src/tools/java/org/junit/api/tools/MarkdownApiReportWriter.java
================================================
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
package org.junit.api.tools;
import java.io.PrintWriter;
import java.nio.CharBuffer;
/**
* @since 1.0
*/
class MarkdownApiReportWriter extends AbstractApiReportWriter {
private static final String MARKDOWN_FORMAT = "%-" + NAME_COLUMN_WIDTH + "s | %-12s%n";
MarkdownApiReportWriter(ApiReport apiReport) {
super(apiReport);
}
@Override
protected String h1(String header) {
return "# " + header;
}
@Override
protected String h2(String header) {
return "## " + header;
}
@Override
protected String h3(String header) {
return "### " + header;
}
@Override
protected String h4(String header) {
return "#### " + header;
}
@Override
protected String code(String element) {
return "`" + element + "`";
}
@Override
protected String italic(String element) {
return "_" + element + "_";
}
@Override
protected void printDeclarationTableHeader(PrintWriter out) {
out.printf(MARKDOWN_FORMAT, "Name", "Since");
out.printf(MARKDOWN_FORMAT, dashes(NAME_COLUMN_WIDTH), dashes(12));
}
private String dashes(int length) {
return CharBuffer.allocate(length).toString().replace('\0', '-');
}
@Override
protected void printDeclarationTableRow(Declaration declaration, PrintWriter out) {
out.printf(MARKDOWN_FORMAT, //
code(declaration.name()) + " " + italic("(" + declaration.kind() + ")"), //
code(declaration.since()) //
);
}
@Override
protected void printDeclarationTableFooter(PrintWriter out) {
/* no-op */
}
}
================================================
FILE: documentation/src/tools/java/org/junit/api/tools/package-info.java
================================================
/**
* Tools to generate reports based on {@link org.apiguardian.api.API} annotations.
*/
package org.junit.api.tools;
================================================
FILE: gradle/base/code-generator-model/.gitignore
================================================
/bin/
================================================
FILE: gradle/base/code-generator-model/build.gradle.kts
================================================
plugins {
`kotlin-dsl`
}
================================================
FILE: gradle/base/code-generator-model/src/main/kotlin/junitbuild/generator/model/JRE.kt
================================================
package junitbuild.generator.model
data class JRE(val version: Int, val since: String?)
================================================
FILE: gradle/base/code-generator-model/src/main/resources/jre.yaml
================================================
- version: 8
- version: 9
- version: 10
- version: 11
- version: 12
since: '5.4'
- version: 13
since: '5.4'
- version: 14
since: '5.5'
- version: 15
since: '5.6'
- version: 16
since: '5.7'
- version: 17
since: '5.7.1'
- version: 18
since: '5.8.1'
- version: 19
since: '5.9'
- version: 20
since: '5.9'
- version: 21
since: '5.9.2'
- version: 22
since: '5.10'
- version: 23
since: '5.11'
- version: 24
since: '5.11'
- version: 25
since: '5.11.4'
- version: 26
since: '5.13.2'
- version: 27
since: '6.1'
================================================
FILE: gradle/base/dsl-extensions/.gitignore
================================================
/bin/
================================================
FILE: gradle/base/dsl-extensions/build.gradle.kts
================================================
plugins {
`kotlin-dsl`
}
================================================
FILE: gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/DependencyExtensions.kt
================================================
package junitbuild.extensions
import org.gradle.api.provider.Provider
import org.gradle.plugin.use.PluginDependency
// see https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers
val Provider.markerCoordinates: Provider
get() = map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}" }
================================================
FILE: gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/ProjectExtensions.kt
================================================
package junitbuild.extensions
import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.the
val Project.javaModuleName: String
get() = toModuleName(name)
val ProjectDependency.javaModuleName: String
get() = toModuleName(name)
private fun toModuleName(name: String) = "org.${name.replace('-', '.')}"
fun Project.dependencyProject(dependency: ProjectDependency) =
project(dependency.path)
fun Project.requiredVersionFromLibs(name: String) =
libsVersionCatalog.findVersion(name).get().requiredVersion
fun Project.dependencyFromLibs(name: String) =
libsVersionCatalog.findLibrary(name).get()
fun Project.bundleFromLibs(name: String) =
libsVersionCatalog.findBundle(name).get()
private val Project.libsVersionCatalog: VersionCatalog
get() = the().named("libs")
================================================
FILE: gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/StringExtensions.kt
================================================
package junitbuild.extensions
import java.util.Locale
fun String.capitalized() = replaceFirstChar {
it.uppercase(Locale.US)
}
================================================
FILE: gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/TaskExtensions.kt
================================================
package junitbuild.extensions
import org.gradle.api.Task
import org.gradle.api.file.ArchiveOperations
import org.gradle.internal.os.OperatingSystem
import org.gradle.kotlin.dsl.newInstance
import javax.inject.Inject
fun Task.trackOperationSystemAsInput() =
inputs.property("os", OperatingSystem.current().familyName)
fun Task.withArchiveOperations(action: (ArchiveOperations) -> T): T =
archiveOperations.run { action(this) }
private val Task.archiveOperations: ArchiveOperations
get() = project.objects.newInstance(DummyObject::class).archiveOperations
private abstract class DummyObject {
@get:Inject
abstract val archiveOperations: ArchiveOperations
}
================================================
FILE: gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/VersionExtensions.kt
================================================
package junitbuild.extensions
fun Any.isSnapshot(): Boolean = toString().contains("SNAPSHOT")
================================================
FILE: gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/junitbuild.dsl-extensions.gradle.kts
================================================
// Just a dummy plugin to get the extensions on the classpath of downstream builds
================================================
FILE: gradle/base/gradle.properties
================================================
group = junitbuild.base
================================================
FILE: gradle/base/settings.gradle.kts
================================================
rootProject.name = "base"
dependencyResolutionManagement {
repositories {
gradlePluginPortal()
}
}
include("code-generator-model")
include("dsl-extensions")
================================================
FILE: gradle/config/checkstyle/checkstyleMain.xml
================================================
================================================
FILE: gradle/config/checkstyle/checkstyleNohttp.xml
================================================
================================================
FILE: gradle/config/checkstyle/checkstyleTest.xml
================================================
================================================
FILE: gradle/config/checkstyle/suppressions.xml
================================================
================================================
FILE: gradle/config/eclipse/junit-eclipse-formatter-settings.xml
================================================
================================================
FILE: gradle/config/eclipse/junit-eclipse.importorder
================================================
#Organize Import Order
0=java
1=javax
2=jdk
3=aQute
4=junit
5=de
6=com
7=example
8=extensions
9=io
10=org
================================================
FILE: gradle/config/roseau/config.yaml
================================================
common:
excludes:
annotations:
- name: org.apiguardian.api.API
args: { status: org.apiguardian.api.API$Status.INTERNAL }
================================================
FILE: gradle/config/spotless/eclipse-public-license-2.0.java
================================================
/*
* Copyright $YEAR the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/
================================================
FILE: gradle/gradle-daemon-jvm.properties
================================================
#This file is generated by updateDaemonJvm
toolchainVersion=25
================================================
FILE: gradle/libs.versions.toml
================================================
[versions]
ant = "1.10.17"
apiguardian = "1.1.2"
assertj = "3.27.7"
bnd = "7.2.3"
checkstyle = "13.4.2"
eclipse = "4.39.0"
jackson = "3.1.3"
jacoco = "0.8.14"
jmh = "1.37"
junit4 = "4.13.2"
junit4Min = "4.12"
ktlint = "1.8.0"
log4j = "2.26.0"
opentest4j = "1.3.0"
openTestReporting = "0.2.5"
snapshotTests = "1.11.0"
surefire = "3.5.5"
xmlunit = "2.11.0"
[libraries]
ant = { module = "org.apache.ant:ant", version.ref = "ant" }
ant-junit = { module = "org.apache.ant:ant-junit", version.ref = "ant" }
ant-junitlauncher = { module = "org.apache.ant:ant-junitlauncher", version.ref = "ant" }
apiguardian = { module = "org.apiguardian:apiguardian-api", version.ref = "apiguardian" }
archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.4.2" }
assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" }
bndlib = { module = "biz.aQute.bnd:biz.aQute.bndlib", version.ref = "bnd" }
byteBuddy = { module = "net.bytebuddy:byte-buddy", version = "1.18.8" }
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.184" }
commons-io = { module = "commons-io:commons-io", version = "2.22.0" }
error-prone-contrib = { module = "tech.picnic.error-prone-support:error-prone-contrib", version = "0.29.0" }
error-prone-core = { module = "com.google.errorprone:error_prone_core", version = "2.49.0" }
fastcsv = { module = "de.siegmar:fastcsv", version = "4.2.0" }
groovy = { module = "org.apache.groovy:groovy", version = "5.0.5" }
groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" }
hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" }
jackson-dataformat-yaml = { module = "tools.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" }
jackson-module-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
jaxb-api = { module = "jakarta.xml.bind:jakarta.xml.bind-api", version = "4.0.5" }
jaxb-runtime = { module = "org.glassfish.jaxb:jaxb-runtime", version = "4.0.8" }
jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" }
jimfs = { module = "com.google.jimfs:jimfs", version = "1.3.1" }
jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
jmh-generator-annprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" }
joox = { module = "org.jooq:joox", version = "2.0.1" }
jspecify = { module = "org.jspecify:jspecify", version = "1.0.0" }
jte = { module = "gg.jte:jte", version = "3.2.4" }
junit4 = { module = "junit:junit", version = { require = "[4.12,)", prefer = "4.13.2" } }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.11.0" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version = "1.11.0" }
log4j-bom = { module = "org.apache.logging.log4j:log4j-bom", version.ref = "log4j" }
log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" }
log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" }
maven = { module = "org.apache.maven:apache-maven", version = "3.9.15" }
mavenSurefirePlugin = { module = "org.apache.maven.plugins:maven-surefire-plugin", version.ref = "surefire" }
memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.8.2" }
mockito-bom = { module = "org.mockito:mockito-bom", version = "5.23.0" }
mockito-core = { module = "org.mockito:mockito-core" }
mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter" }
nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version = "0.0.11" }
nullaway = { module = "com.uber.nullaway:nullaway", version = "0.13.4" }
opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" }
openTestReporting-cli = { module = "org.opentest4j.reporting:open-test-reporting-cli", version.ref = "openTestReporting" }
openTestReporting-events = { module = "org.opentest4j.reporting:open-test-reporting-events", version.ref = "openTestReporting" }
openTestReporting-tooling-core = { module = "org.opentest4j.reporting:open-test-reporting-tooling-core", version.ref = "openTestReporting" }
openTestReporting-tooling-spi = { module = "org.opentest4j.reporting:open-test-reporting-tooling-spi", version.ref = "openTestReporting" }
picocli = { module = "info.picocli:picocli", version = "4.7.7" }
roseau-cli = { module = "io.github.alien-tools:roseau-cli", version = "0.5.0" }
slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.17" }
snapshotTests-junit5 = { module = "de.skuzzle.test:snapshot-tests-junit5", version.ref = "snapshotTests" }
snapshotTests-xml = { module = "de.skuzzle.test:snapshot-tests-xml", version.ref = "snapshotTests" }
spock1 = { module = "org.spockframework:spock-core", version = "1.3-groovy-2.5" }
xmlunit-assertj = { module = "org.xmlunit:xmlunit-assertj3", version.ref = "xmlunit" }
xmlunit-placeholders = { module = "org.xmlunit:xmlunit-placeholders", version.ref = "xmlunit" }
xmlunit-jakarta-jaxb-impl = { module = "org.xmlunit:xmlunit-jakarta-jaxb-impl", version.ref = "xmlunit" }
testingAnnotations = { module = "com.gradle:develocity-testing-annotations", version = "2.0.1" }
woodstox = { module = "com.fasterxml.woodstox:woodstox-core", version = "7.1.1" }
# Only declared here so Dependabot knows when to update the referenced versions
eclipse-platform = { module = "org.eclipse.platform:org.eclipse.platform", version.ref = "eclipse" }
jacoco = { module = "org.jacoco:jacoco", version.ref = "jacoco" }
junit4-latest = { module = "junit:junit", version.ref = "junit4" }
junit4-bundle = { module = "org.apache.servicemix.bundles:org.apache.servicemix.bundles.junit", version = "4.13.2_1" }
ktlint-cli = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
[bundles]
ant = ["ant", "ant-junit", "ant-junitlauncher"]
log4j = ["log4j-core", "log4j-jul"]
xmlunit = ["xmlunit-assertj", "xmlunit-placeholders", "xmlunit-jakarta-jaxb-impl", "jaxb-api", "jaxb-runtime"]
[plugins]
bnd = { id = "biz.aQute.bnd", version.ref = "bnd" }
buildParameters = { id = "org.gradlex.build-parameters", version = "1.4.5" }
commonCustomUserData = { id = "com.gradle.common-custom-user-data-gradle-plugin", version = "2.6.0" }
develocity = { id = "com.gradle.develocity", version = "4.4.1" }
download = { id = "de.undercouch.download", version = "5.7.0" }
errorProne = { id = "net.ltgt.errorprone", version = "5.1.0" }
foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "1.0.0" }
jmh = { id = "me.champeau.jmh", version = "0.7.3" }
# check if workaround in gradle.properties can be removed when updating
kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.3.21" }
nmcp-settings = { id = "com.gradleup.nmcp.settings", version = "1.5.0" }
node = { id = "com.github.node-gradle.node", version = "7.1.0" }
nullaway = { id = "net.ltgt.nullaway", version = "3.0.0" }
plantuml = { id = "io.freefair.plantuml", version = "9.5.0" }
shadow = { id = "com.gradleup.shadow", version = "9.4.1" }
spotless = { id = "com.diffplug.spotless", version = "8.4.0" }
spring-antora = { id = "io.spring.antora.generate-antora-yml", version = "0.0.1" }
================================================
FILE: gradle/plugins/antora/build.gradle.kts
================================================
import junitbuild.extensions.markerCoordinates
plugins {
`kotlin-dsl`
}
dependencies {
implementation(projects.buildParameters)
implementation(libs.plugins.node.markerCoordinates)
constraints {
implementation("com.fasterxml.jackson.core:jackson-core") {
version {
require("2.21.1")
}
because("Workaround for GHSA-72hv-8253-57qq")
}
}
implementation(libs.plugins.spring.antora.markerCoordinates)
constraints {
implementation("org.yaml:snakeyaml") {
version {
require("2.0")
}
because("Workaround for CVE-2022-1471")
}
}
}
================================================
FILE: gradle/plugins/antora/src/main/kotlin/junitbuild/antora/AntoraConfiguration.kt
================================================
package junitbuild.antora
import org.gradle.api.file.DirectoryProperty
interface AntoraConfiguration {
val siteDir: DirectoryProperty
}
================================================
FILE: gradle/plugins/antora/src/main/kotlin/junitbuild.antora-conventions.gradle.kts
================================================
import com.github.gradle.node.npm.task.NpxTask
import junitbuild.antora.AntoraConfiguration
plugins {
id("com.github.node-gradle.node")
id("io.spring.antora.generate-antora-yml")
id("junitbuild.build-parameters")
}
val configuration = extensions.create("antora")
val siteDir = layout.buildDirectory.dir("antora-site")
configuration.siteDir.value(siteDir).finalizeValue()
repositories {
// Redefined here because the Node.js plugin adds a repo
mavenCentral()
}
tasks.register("generateAntoraResources") {
dependsOn("generateAntoraYml")
}
val generateAntoraPlaybook by tasks.registering(Copy::class) {
val gitRepoRoot = providers.exec {
commandLine("git", "worktree", "list", "--porcelain", "-z")
}.standardOutput.asText.map { it.substringBefore('\u0000').substringAfter(' ') }
inputs.property("gitRepoRoot", gitRepoRoot)
val gitBranchName = providers.exec {
commandLine("git", "rev-parse", "--abbrev-ref", "HEAD")
}.standardOutput.asText.map { it.trim() }
inputs.property("gitBranchName", gitBranchName)
from(layout.projectDirectory.file("antora-playbook.yml").asFile)
filter { line ->
var result = line
if (line.contains("@GIT_REPO_ROOT@")) {
result = result.replace("@GIT_REPO_ROOT@", gitRepoRoot.get())
}
if (line.contains("@GIT_BRANCH_NAME@")) {
result = result.replace("@GIT_BRANCH_NAME@", gitBranchName.get())
}
return@filter result
}
into(layout.buildDirectory.dir("antora-playbook"))
}
node {
download = buildParameters.antora.downloadNode
version = providers.fileContents(layout.projectDirectory.file(".tool-versions")).asText.map {
it.substringAfter("nodejs").trim()
}
}
tasks.npmInstall {
args.addAll("--no-audit", "--no-package-lock", "--no-fund")
}
tasks.register("antora") {
dependsOn(tasks.npmInstall)
description = "Runs Antora to generate a documentation site described by the playbook file."
command = "antora"
args.addAll("--clean", "--stacktrace", "--fetch", "--log-format=pretty", "--log-level=all")
args.add("--to-dir")
args.add(siteDir.map { it.asFile.toRelativeString(layout.projectDirectory.asFile) })
outputs.dir(siteDir)
outputs.upToDateWhen { false } // not all inputs are tracked
val playbook = generateAntoraPlaybook.map { it.rootSpec.destinationDir!!.resolve("antora-playbook.yml") }
args.add(playbook.map { it.toRelativeString(layout.projectDirectory.asFile) })
inputs.file(playbook)
execOverrides {
environment["IS_TTY"] = true
}
}
================================================
FILE: gradle/plugins/backward-compatibility/build.gradle.kts
================================================
import junitbuild.extensions.markerCoordinates
plugins {
`kotlin-dsl`
}
dependencies {
implementation("junitbuild.base:dsl-extensions")
implementation(libs.plugins.download.markerCoordinates)
implementation(libs.jackson.dataformat.yaml)
}
================================================
FILE: gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild/compatibility/BackwardCompatibilityChecksExtension.kt
================================================
package junitbuild.compatibility
import org.gradle.api.provider.Property
abstract class BackwardCompatibilityChecksExtension {
abstract val enabled: Property
abstract val previousVersion: Property
}
================================================
FILE: gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild/compatibility/roseau/RoseauDiff.kt
================================================
package junitbuild.compatibility.roseau
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.CompileClasspath
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.assign
import org.gradle.process.ExecOperations
import tools.jackson.databind.ObjectMapper
import tools.jackson.dataformat.yaml.YAMLFactory
import tools.jackson.dataformat.yaml.YAMLWriteFeature.WRITE_DOC_START_MARKER
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import javax.inject.Inject
@CacheableTask
abstract class RoseauDiff : DefaultTask() {
@get:Inject
abstract val execOperations: ExecOperations
@get:Classpath
abstract val toolClasspath: ConfigurableFileCollection
@get:CompileClasspath
abstract val libraryClasspath: ConfigurableFileCollection
@get:CompileClasspath
abstract val v1: RegularFileProperty
@get:CompileClasspath
abstract val v2: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val configFile: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
@get:Optional
abstract val acceptedChangesCsvFile: RegularFileProperty
@get:OutputDirectory
abstract val reportDir: DirectoryProperty
@TaskAction
fun run() {
val reportDir = reportDir.get().asFile.absoluteFile
val reports = listOf(
Report(reportDir.resolve("breaking-changes.html"), Report.Format.HTML),
Report(reportDir.resolve("breaking-changes.csv"), Report.Format.CSV)
)
reports.forEach { report -> report.file.delete() }
val effectiveConfigFile = writeEffectiveConfigFile(reports)
val output = ByteArrayOutputStream()
val result = execOperations.javaexec {
mainClass = "io.github.alien.roseau.cli.RoseauCLI"
classpath = toolClasspath
args(
"--classpath", libraryClasspath.asPath,
"--v1", v1.get().asFile.absolutePath,
"--v2", v2.get().asFile.absolutePath,
"--diff",
"--fail-on-bc",
"--config", effectiveConfigFile.absolutePath,
"-vv",
)
if (acceptedChangesCsvFile.isPresent) {
args("--ignored", acceptedChangesCsvFile.get().asFile.absolutePath)
}
standardOutput = output
errorOutput = output
isIgnoreExitValue = true
}
if (result.exitValue != 0) {
System.out.write(output.toByteArray())
System.out.flush()
if (result.exitValue == 1) {
throw GradleException("Breaking API changes detected")
}
result.assertNormalExitValue()
}
}
private fun writeEffectiveConfigFile(reports: List): File {
val effectiveConfigFile = temporaryDir.resolve("roseau.yaml")
configFile.get().asFile.copyTo(effectiveConfigFile, overwrite = true)
FileOutputStream(effectiveConfigFile, true).bufferedWriter().use { writer ->
val yamlFactory = YAMLFactory.builder().disable(WRITE_DOC_START_MARKER).build()
val mapper = ObjectMapper(yamlFactory)
mapper.writeValue(
writer, mapOf(
"reports" to reports
)
)
}
return effectiveConfigFile
}
private data class Report(val file: File, val format: Format) {
enum class Format {
HTML, CSV
}
}
}
================================================
FILE: gradle/plugins/backward-compatibility/src/main/kotlin/junitbuild.backward-compatibility.gradle.kts
================================================
import de.undercouch.gradle.tasks.download.Download
import junitbuild.compatibility.BackwardCompatibilityChecksExtension
import junitbuild.compatibility.roseau.RoseauDiff
import junitbuild.extensions.dependencyFromLibs
plugins {
java
id("de.undercouch.download")
}
val roseauDependencies = configurations.dependencyScope("roseau")
val roseauClasspath = configurations.resolvable("roseauClasspath") {
extendsFrom(roseauDependencies.get())
}
dependencies {
roseauDependencies(dependencyFromLibs("roseau-cli"))
roseauDependencies(platform(dependencyFromLibs("log4j-bom"))) {
because("Workaround for CVE-2025-68161")
}
constraints {
roseauDependencies("org.apache.commons:commons-lang3") {
version {
require("3.18.0")
}
because("Workaround for CVE-2025-48924")
}
roseauDependencies("com.fasterxml.jackson.core:jackson-core") {
version {
require("2.21.1")
}
because("Workaround for GHSA-72hv-8253-57qq")
}
}
}
val extension = extensions.create("backwardCompatibilityChecks").apply {
enabled.convention(true)
previousVersion.apply {
convention(providers.gradleProperty("apiBaselineVersion"))
finalizeValueOnRead()
}
}
val downloadPreviousReleaseJar by tasks.registering(Download::class) {
if (gradle.startParameter.isOffline) {
enabled = false
}
onlyIf { extension.enabled.get() }
val previousVersion = extension.previousVersion.get()
src("https://repo1.maven.org/maven2/${project.group.toString().replace(".", "/")}/${project.name}/$previousVersion/${project.name}-$previousVersion.jar")
dest(layout.buildDirectory.dir("previousRelease"))
overwrite(false)
quiet(true)
retries(2)
outputs.cacheIf { true }
}
val roseauCsvFile = layout.buildDirectory.file("reports/roseau/breaking-changes.csv")
val roseau by tasks.registering(RoseauDiff::class) {
if (gradle.startParameter.isOffline) {
enabled = false
}
onlyIf { extension.enabled.get() }
toolClasspath.from(roseauClasspath)
libraryClasspath.from(configurations.compileClasspath)
v1 = downloadPreviousReleaseJar.map { it.outputFiles.single() }
v2 = tasks.jar.flatMap { it.archiveFile }.map { it.asFile }
configFile = rootProject.layout.projectDirectory.file("gradle/config/roseau/config.yaml")
rootProject.layout.projectDirectory.file("gradle/config/roseau/accepted-breaking-changes.csv").asFile.let {
if (it.exists()) {
acceptedChangesCsvFile = it
}
}
reportDir = layout.buildDirectory.dir("reports/roseau")
}
val checkBackwardCompatibility by tasks.registering {
dependsOn(roseau)
}
tasks.check {
dependsOn(checkBackwardCompatibility)
}
================================================
FILE: gradle/plugins/build-parameters/build.gradle.kts
================================================
plugins {
alias(libs.plugins.buildParameters)
}
group = "junitbuild"
buildParameters {
pluginId("junitbuild.build-parameters")
bool("ci") {
description = "Whether or not this build is running in a CI environment"
defaultValue = false
fromEnvironment()
}
group("javaToolchain") {
description = "Parameters controlling the Java toolchain used for compiling code and running tests"
integer("version") {
description = "JDK version"
}
string("implementation") {
description = "JDK implementation (for example, 'j9')"
}
}
group("documentation") {
description = "Parameters controlling how the documentation is built"
bool("replaceCurrentDocs") {
description = "The documentation that is being deployed will replace what's currently deployed as 'current'"
defaultValue = false
}
}
group("junit") {
group("develocity") {
description = "Parameters controlling Develocity features"
group("buildCache") {
string("server") {
description =
"Remote build cache server address (protocol and hostname), e.g. https://eu-build-cache-ge.junit.org"
}
bool("pushEnabled") {
description =
"Whether to push to the remote build cache"
defaultValue = false
}
}
group("predictiveTestSelection") {
bool("enabled") {
description = "Whether or not to use Predictive Test Selection for selecting tests to execute"
defaultValue = true
}
bool("selectRemainingTests") {
// see https://docs.gradle.com/develocity/predictive-test-selection/#gradle-selection-mode
description = "Whether or not to use PTS' 'remaining tests' selection mode"
defaultValue = false
}
}
group("testDistribution") {
bool("enabled") {
description = "Whether or not to use Test Distribution for executing tests"
defaultValue = false
fromEnvironment()
}
integer("maxLocalExecutors") {
description = "How many local executors to use for executing tests"
defaultValue = 1
}
integer("maxRemoteExecutors") {
description = "How many remote executors to request for executing tests"
}
}
}
}
group("testing") {
description = "Testing related parameters"
bool("dryRun") {
description = "Enables dry run mode for tests"
defaultValue = false
}
bool("enableJaCoCo") {
description = "Enables JaCoCo test coverage reporting"
defaultValue = true
}
bool("enableJFR") {
description = "Enables Java Flight Recorder functionality"
defaultValue = false
}
integer("retries") {
description = "Configures the number of times failing test are retried"
}
bool("hideOpenTestReportHtmlGeneratorOutput") {
description = "Whether or not to hide the output of the OpenTestReportHtmlGenerator"
defaultValue = true
}
}
group("publishing") {
bool("signArtifacts") {
description = "Sign artifacts before publishing them to Maven repos"
}
string("group") {
description = "Group ID for published Maven artifacts"
}
}
group("jitpack") {
string("version") {
description = "The version computed by Jitpack"
}
}
group("manifest") {
string("buildTimestamp") {
description = "Overrides the value of the 'Build-Date' and 'Build-Time' jar manifest entries. Can be set as a String (e.g. '2023-11-05 17:49:13.996+0100') or as seconds since the epoch."
fromEnvironment("SOURCE_DATE_EPOCH") // see https://reproducible-builds.org/docs/source-date-epoch/
}
string("builtBy") {
description = "Overrides the value of the 'Built-By' jar manifest entry"
}
string("createdBy") {
description = "Overrides the value of the 'Created-By' jar manifest entry"
}
}
group("antora") {
bool("downloadNode") {
description = "Whether to download Node.js from the Gradle build"
defaultValue = true
}
}
}
================================================
FILE: gradle/plugins/code-generator/build.gradle.kts
================================================
plugins {
`kotlin-dsl`
}
dependencies {
implementation("junitbuild.base:code-generator-model")
implementation("junitbuild.base:dsl-extensions")
implementation(projects.common)
implementation(libs.jackson.dataformat.yaml)
implementation(libs.jackson.module.kotlin)
implementation(libs.jte)
}
================================================
FILE: gradle/plugins/code-generator/src/main/kotlin/junitbuild/generator/GenerateJreRelatedSourceCode.kt
================================================
package junitbuild.generator
import gg.jte.ContentType
import gg.jte.TemplateEngine
import gg.jte.output.FileOutput
import gg.jte.resolve.DirectoryCodeResolver
import junitbuild.generator.model.JRE
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.TaskAction
import tools.jackson.core.type.TypeReference
import tools.jackson.dataformat.yaml.YAMLMapper
import tools.jackson.module.kotlin.KotlinModule
import java.time.Year
@CacheableTask
abstract class GenerateJreRelatedSourceCode : DefaultTask() {
@get:InputDirectory
@get:SkipWhenEmpty
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val templateDir: DirectoryProperty
@get:OutputDirectory
abstract val targetDir: DirectoryProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val licenseHeaderFile: RegularFileProperty
@get:Input
abstract val licenseHeaderYear: Property
@get:Input
@get:Optional
abstract val maxVersion: Property
@get:Input
@get:Optional
abstract val fileNamePrefix: Property
@get:Input
abstract val additionalTemplateParameters: MapProperty
@TaskAction
fun generateSourceCode() {
val mainTargetDir = targetDir.get().asFile
mainTargetDir.deleteRecursively()
val templateDir = templateDir.get().asFile
val codeResolver = DirectoryCodeResolver(templateDir.toPath())
val templateEngine =
TemplateEngine.create(codeResolver, temporaryDir.toPath(), ContentType.Plain, javaClass.classLoader)
val templates = templateDir.walkTopDown()
.filter { it.extension == "jte" }
.map { it.relativeTo(templateDir) }
.toList()
if (templates.isNotEmpty()) {
var jres = javaClass.getResourceAsStream("/jre.yaml").use { input ->
val mapper = YAMLMapper.builder()
.addModule(KotlinModule.Builder().build())
.build()
mapper.readValue(input, object : TypeReference>() {})
}
if (maxVersion.isPresent) {
jres = jres.filter { it.version <= maxVersion.get() }
}
val minRuntimeVersion = 17
val supportedJres = jres.filter { it.version >= minRuntimeVersion }
val licenseHeader = licenseHeaderFile.asFile.get().readText().trimEnd()
.replace($$"$YEAR", licenseHeaderYear.get().toString()) + "\n"
val params = additionalTemplateParameters.get() + mapOf(
"minRuntimeVersion" to minRuntimeVersion,
"allJres" to jres,
"supportedJres" to supportedJres,
"supportedJresSortedByStringValue" to supportedJres.sortedBy { it.version.toString() },
"licenseHeader" to licenseHeader,
)
templates.forEach {
val fileName = "${fileNamePrefix.getOrElse("")}${it.nameWithoutExtension}"
val targetFile = mainTargetDir.toPath().resolve(it.resolveSibling(fileName).path)
FileOutput(targetFile).use { output ->
// JTE does not support Windows paths, so we need to replace them
val safePath = it.path.replace('\\', '/')
templateEngine.render(safePath, params, output)
}
}
}
}
}
================================================
FILE: gradle/plugins/code-generator/src/main/kotlin/junitbuild.code-generator.gradle.kts
================================================
import junitbuild.extensions.dependencyFromLibs
import junitbuild.generator.GenerateJreRelatedSourceCode
import java.time.Year
plugins {
java
}
val templates by sourceSets.creating
val templatesCompileOnly = configurations[templates.compileOnlyConfigurationName]
dependencies {
templatesCompileOnly(dependencyFromLibs("jte"))
templatesCompileOnly("junitbuild.base:code-generator-model")
}
val license: License by rootProject.extra
val rootTargetDir = layout.buildDirectory.dir("generated/sources/jte")
val generateCode by tasks.registering {
dependsOn(tasks.withType())
group = LifecycleBasePlugin.BUILD_GROUP
description = "Generates JRE-related source code."
}
tasks.withType