diff --git a/documentation/modules/ROOT/pages/extensions/keeping-state-in-extensions.adoc b/documentation/modules/ROOT/pages/extensions/keeping-state-in-extensions.adoc index 082da03fa2ed..a7a111ac4eac 100644 --- a/documentation/modules/ROOT/pages/extensions/keeping-state-in-extensions.adoc +++ b/documentation/modules/ROOT/pages/extensions/keeping-state-in-extensions.adoc @@ -43,11 +43,27 @@ available for backward compatibility. An example implementation of `AutoCloseable` is shown below, using an `HttpServer` resource. -[source,java,indent=0] .`_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 @@ -55,22 +71,54 @@ resource. For this example it might be prudent to store it at root level and ins it lazily to ensure it's only created once per test run and reused across different test classes and methods. -[source,java,indent=0] .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] +---- +-- +==== -[source,java,indent=0] .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. @@ -79,6 +127,11 @@ If you're developing an extension that needs to support both JUnit Jupiter 5.13+ 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 { @@ -88,7 +141,22 @@ public class MyResource implements Store.CloseableResource, AutoCloseable { } } ---- +-- + +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. -==== +====== diff --git a/documentation/modules/ROOT/pages/extensions/parameter-resolution.adoc b/documentation/modules/ROOT/pages/extensions/parameter-resolution.adoc index f195b5af80bb..1cdf324577db 100644 --- a/documentation/modules/ROOT/pages/extensions/parameter-resolution.adoc +++ b/documentation/modules/ROOT/pages/extensions/parameter-resolution.adoc @@ -60,38 +60,102 @@ registered for a test, a `ParameterResolutionException` will be thrown, with a message to indicate that competing resolvers have been discovered. See the following example: -[source,java,indent=0] .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. -[source,java,indent=0] .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. -[source,java,indent=0] .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: -[source,java,indent=0] .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 diff --git a/documentation/modules/ROOT/pages/extensions/registering-extensions.adoc b/documentation/modules/ROOT/pages/extensions/registering-extensions.adoc index e25eb404f063..82486c318df1 100644 --- a/documentation/modules/ROOT/pages/extensions/registering-extensions.adoc +++ b/documentation/modules/ROOT/pages/extensions/registering-extensions.adoc @@ -19,6 +19,11 @@ For example, to register a `WebServerExtension` for a particular test method, yo 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 @@ -29,10 +34,32 @@ void getProductList(@WebServerUrl String serverUrl) { 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) @@ -40,9 +67,28 @@ 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 }) @@ -50,9 +96,28 @@ 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) @@ -61,6 +126,21 @@ class MySecondTests { // ... } ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +@ExtendWith(DatabaseExtension::class) +@ExtendWith(WebServerExtension::class) +class MySecondTests { + // ... +} +---- +-- +==== [TIP] .Extension Registration Order @@ -76,6 +156,11 @@ _xref:writing-tests/annotations.adoc#annotations[composed annotation]_ and use ` _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 }) @@ -84,6 +169,20 @@ can be used in place of `@ExtendWith({ DatabaseExtension.class, WebServerExtensi 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 @@ -94,15 +193,47 @@ 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 @@ -118,10 +249,26 @@ Specifically, `RandomNumberExtension` implements the following extension APIs: - `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 @@ -255,11 +402,28 @@ to the static `forPath()` factory method in the `DocumentationExtension`. The co 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 diff --git a/documentation/modules/ROOT/pages/extensions/test-lifecycle-callbacks.adoc b/documentation/modules/ROOT/pages/extensions/test-lifecycle-callbacks.adoc index 2b0190598f5e..8a356a7eddcb 100644 --- a/documentation/modules/ROOT/pages/extensions/test-lifecycle-callbacks.adoc +++ b/documentation/modules/ROOT/pages/extensions/test-lifecycle-callbacks.adoc @@ -35,20 +35,52 @@ time of a test method. `TimingExtension` implements both `BeforeTestExecutionCal and `AfterTestExecutionCallback` in order to time and log the test execution. [[timing-extension]] -[source,java,indent=0] .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. -[source,java,indent=0] .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. diff --git a/documentation/modules/ROOT/pages/writing-tests/annotations.adoc b/documentation/modules/ROOT/pages/writing-tests/annotations.adoc index 33d1c1e9f7a7..10372867cdfa 100644 --- a/documentation/modules/ROOT/pages/writing-tests/annotations.adoc +++ b/documentation/modules/ROOT/pages/writing-tests/annotations.adoc @@ -136,13 +136,34 @@ xref:writing-tests/tagging-and-filtering.adoc[]), you can create a custom _compo 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 @@ -151,18 +172,54 @@ 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 @@ -170,3 +227,17 @@ void myFastTest() { // ... } ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +@FastTest +fun myFastTest() { + // ... +} +---- +-- +==== diff --git a/documentation/modules/ROOT/pages/writing-tests/built-in-extensions.adoc b/documentation/modules/ROOT/pages/writing-tests/built-in-extensions.adoc index f266bc33b70f..9cdf245c2874 100644 --- a/documentation/modules/ROOT/pages/writing-tests/built-in-extensions.adoc +++ b/documentation/modules/ROOT/pages/writing-tests/built-in-extensions.adoc @@ -19,30 +19,78 @@ For example, the following test declares a parameter annotated with `@TempDir` f single test method, creates and writes to a file in the temporary directory, and checks its content. -[source,java,indent=0] .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. -[source,java,indent=0] .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. -[source,java,indent=0] .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 @@ -53,11 +101,27 @@ 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. -[source,java,indent=0] .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 @@ -80,39 +144,103 @@ 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. -[source,java,indent=0] .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. -[source,java,indent=0] .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)`. -[source,java,indent=0] .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. -[source,java,indent=0] .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. @@ -157,11 +285,27 @@ Jupiter ships with two built-in deletion strategies: The following example uses `{TempDirDeletionStrategyIgnoreFailures}` so that any deletion failures are only logged. -[source,java,indent=0] .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 @@ -219,11 +363,27 @@ The following example demonstrates how to annotate an instance field with `@Auto that the resource is automatically closed after test execution. In this example, we assume that the default `@TestInstance(Lifecycle.PER_METHOD)` semantics apply. -[source,java,indent=0] .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. @@ -244,10 +404,26 @@ default value is restored. 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`] @@ -260,10 +436,26 @@ can create an instance: 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 @@ -272,20 +464,52 @@ 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. @@ -296,27 +520,75 @@ 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. @@ -364,32 +636,95 @@ restoration is <> via 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] ==== @@ -429,10 +764,26 @@ and fail if any were detected. For classes that extend `Properties` it is assume 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`, @@ -450,17 +801,49 @@ lifecycle, ensuring that changes are not visible to `SomeOtherTestClass`. Note t 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 @@ -471,10 +854,26 @@ you need to test an image generation utility that takes configuration from syste 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] ==== diff --git a/documentation/modules/ROOT/pages/writing-tests/conditional-test-execution.adoc b/documentation/modules/ROOT/pages/writing-tests/conditional-test-execution.adoc index 712c1a06b3ff..a317b50b893e 100644 --- a/documentation/modules/ROOT/pages/writing-tests/conditional-test-execution.adoc +++ b/documentation/modules/ROOT/pages/writing-tests/conditional-test-execution.adoc @@ -60,18 +60,50 @@ architecture, or combination of both via the `{EnabledOnOs}` and `{DisabledOnOs} annotations. [[os-demo]] -[source,java,indent=0] .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]] -[source,java,indent=0] .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 @@ -85,10 +117,26 @@ lower bound and `JRE.OTHER` as the upper bound, which allows usage of half open 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` @@ -102,10 +150,26 @@ via the `versions` attributes in `@EnabledOnJre` and `@DisabledOnJre` and via th 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 @@ -117,10 +181,26 @@ typically used when running tests within a native image using the Gradle and Mav 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 @@ -130,10 +210,26 @@ system property via the `{EnabledIfSystemProperty}` and `{DisabledIfSystemProper 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] ==== @@ -151,10 +247,26 @@ 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] ==== @@ -175,21 +287,55 @@ return type and may accept either no arguments or a single `ExtensionContext` ar 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] ==== @@ -214,9 +360,26 @@ method that can be used to determine if the current environment does not support 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") +---- +-- +====== ==== diff --git a/documentation/modules/ROOT/pages/writing-tests/display-names.adoc b/documentation/modules/ROOT/pages/writing-tests/display-names.adoc index cb95bd7d9b02..9fc4dbff5ce6 100644 --- a/documentation/modules/ROOT/pages/writing-tests/display-names.adoc +++ b/documentation/modules/ROOT/pages/writing-tests/display-names.adoc @@ -4,10 +4,40 @@ Test classes and test methods can declare custom display names via `@DisplayName 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] ==== @@ -61,10 +91,26 @@ 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. @@ -82,10 +128,26 @@ With the `IndicativeSentences` display name generator, you can customize the sep 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. @@ -103,10 +165,26 @@ A year 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. diff --git a/documentation/modules/ROOT/pages/writing-tests/test-interfaces-and-default-methods.adoc b/documentation/modules/ROOT/pages/writing-tests/test-interfaces-and-default-methods.adoc index d5425fe0767f..e4773590a1b8 100644 --- a/documentation/modules/ROOT/pages/writing-tests/test-interfaces-and-default-methods.adoc +++ b/documentation/modules/ROOT/pages/writing-tests/test-interfaces-and-default-methods.adoc @@ -7,32 +7,96 @@ test interface or on interface `default` methods _if_ the test interface or test annotated with `@TestInstance(Lifecycle.PER_CLASS)` (see xref:writing-tests/test-instance-lifecycle.adoc[]). Here are some examples. -[source,java] +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] ---- include::example$java/example/testinterface/TestLifecycleLogger.java[tags=user_guide] ---- +-- -[source,java] +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]. -[source,java] +[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. -[source,java] +[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: @@ -51,27 +115,91 @@ Another possible application of this feature is to write tests for interface con For example, you can write tests for how implementations of `Object.equals` or `Comparable.compareTo` should behave as follows. -[source,java] +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] ---- include::example$java/example/defaultmethods/Testable.java[tags=user_guide] ---- +-- -[source,java] +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] +---- +-- +==== -[source,java] +[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. -[source,java] +[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. diff --git a/documentation/modules/ROOT/pages/writing-tests/timeouts.adoc b/documentation/modules/ROOT/pages/writing-tests/timeouts.adoc index 531573c1bc78..0542bc28f15a 100644 --- a/documentation/modules/ROOT/pages/writing-tests/timeouts.adoc +++ b/documentation/modules/ROOT/pages/writing-tests/timeouts.adoc @@ -6,10 +6,26 @@ 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 @@ -117,10 +133,26 @@ does not execute indefinitely. The following example demonstrates how to achieve 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 diff --git a/documentation/src/test/java/example/DefaultLocaleTimezoneExtensionDemo.java b/documentation/src/test/java/example/DefaultLocaleTimezoneExtensionDemo.java index e5e3808f864c..6018c943c05d 100644 --- a/documentation/src/test/java/example/DefaultLocaleTimezoneExtensionDemo.java +++ b/documentation/src/test/java/example/DefaultLocaleTimezoneExtensionDemo.java @@ -54,8 +54,8 @@ void test_with_language_and_country_and_vairant() { } // end::default_locale_language_alternatives[] - @Nested // tag::default_locale_class_level[] + @Nested @DefaultLocale(language = "fr") class MyLocaleTests { @@ -102,8 +102,8 @@ void test_with_long_zone_id() { } // end::default_timezone_zone[] - @Nested // tag::default_timezone_class_level[] + @Nested @DefaultTimeZone("CET") class MyTimeZoneTests { diff --git a/documentation/src/test/java/example/SystemPropertyExtensionDemo.java b/documentation/src/test/java/example/SystemPropertyExtensionDemo.java index 3cb584a7d5b5..8a81e77b95d0 100644 --- a/documentation/src/test/java/example/SystemPropertyExtensionDemo.java +++ b/documentation/src/test/java/example/SystemPropertyExtensionDemo.java @@ -57,8 +57,8 @@ void testClearingAndSettingProperty() { } // end::systemproperty_using_set_and_clear[] - @Nested // tag::systemproperty_using_at_class_level[] + @Nested @ClearSystemProperty(key = "some property") class MySystemPropertyTest { @@ -85,10 +85,10 @@ void parameterizedTest(String value) { @TestClassOrder(ClassOrderer.OrderAnnotation.class) class SystemPropertyRestoreExample { + // tag::systemproperty_class_restore_setup[] @Nested @Order(1) @TestInstance(TestInstance.Lifecycle.PER_CLASS) - // tag::systemproperty_class_restore_setup[] @RestoreSystemProperties class MySystemPropertyRestoreTest { @@ -119,9 +119,9 @@ void isolatedTest2() { } // end::systemproperty_class_restore_setup[] + // tag::systemproperty_class_restore_isolated_class[] @Nested @Order(2) - // tag::systemproperty_class_restore_isolated_class[] @ReadsSystemProperty class SomeOtherTestClass { diff --git a/documentation/src/test/java/example/TimeoutDemo.java b/documentation/src/test/java/example/TimeoutDemo.java index 28bfcfbb536d..658abbda044e 100644 --- a/documentation/src/test/java/example/TimeoutDemo.java +++ b/documentation/src/test/java/example/TimeoutDemo.java @@ -18,8 +18,8 @@ import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.Timeout.ThreadMode; -@Tag("timeout") // tag::user_guide[] +@Tag("timeout") class TimeoutDemo { @BeforeEach diff --git a/documentation/src/test/kotlin/example/kotlin/AutoCloseDemo.kt b/documentation/src/test/kotlin/example/kotlin/AutoCloseDemo.kt new file mode 100644 index 000000000000..76c64f7cd8b2 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/AutoCloseDemo.kt @@ -0,0 +1,34 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/ConditionalTestExecutionDemo.kt b/documentation/src/test/kotlin/example/kotlin/ConditionalTestExecutionDemo.kt new file mode 100644 index 000000000000..1a392a6790af --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/ConditionalTestExecutionDemo.kt @@ -0,0 +1,266 @@ +/* + * 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[] +} diff --git a/documentation/src/test/kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt b/documentation/src/test/kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt new file mode 100644 index 000000000000..b8413cc4e01e --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/DefaultLocaleTimezoneExtensionDemo.kt @@ -0,0 +1,137 @@ +/* + * 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[] + + // tag::default_locale_class_level[] + @Nested + @DefaultLocale(language = "fr") + inner class MyLocaleTests { + @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[] + + // tag::default_timezone_class_level[] + @Nested + @DefaultTimeZone("CET") + inner class MyTimeZoneTests { + @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[] +} diff --git a/documentation/src/test/kotlin/example/kotlin/DisplayNameDemo.kt b/documentation/src/test/kotlin/example/kotlin/DisplayNameDemo.kt new file mode 100644 index 000000000000..ddae0a73b614 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/DisplayNameDemo.kt @@ -0,0 +1,34 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/DisplayNameGeneratorDemo.kt b/documentation/src/test/kotlin/example/kotlin/DisplayNameGeneratorDemo.kt new file mode 100644 index 000000000000..6b235eb40b73 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/DisplayNameGeneratorDemo.kt @@ -0,0 +1,74 @@ +/* + * 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 { + @Nested + // tag::user_guide_replace_underscores[] + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores::class) + inner class A_year_is_not_supported { + @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[] + + @Nested + // tag::user_guide_indicative_sentences[] + @IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores::class) + inner class A_year_is_a_leap_year { + @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[] + + @Nested + // tag::user_guide_custom_sentence_fragments[] + @SentenceFragment("A year is a leap year") + @IndicativeSentencesGeneration + inner class LeapYearTests { + @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[] +} diff --git a/documentation/src/test/kotlin/example/kotlin/ExternalCustomConditionDemo.kt b/documentation/src/test/kotlin/example/kotlin/ExternalCustomConditionDemo.kt new file mode 100644 index 000000000000..c02881deb21c --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/ExternalCustomConditionDemo.kt @@ -0,0 +1,31 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/Fast.kt b/documentation/src/test/kotlin/example/kotlin/Fast.kt new file mode 100644 index 000000000000..92f6ffc134c8 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/Fast.kt @@ -0,0 +1,20 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/FastTest.kt b/documentation/src/test/kotlin/example/kotlin/FastTest.kt new file mode 100644 index 000000000000..d4fd766746f1 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/FastTest.kt @@ -0,0 +1,22 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/HttpServerDemo.kt b/documentation/src/test/kotlin/example/kotlin/HttpServerDemo.kt new file mode 100644 index 000000000000..65768295d304 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/HttpServerDemo.kt @@ -0,0 +1,48 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/PollingTimeoutDemo.kt b/documentation/src/test/kotlin/example/kotlin/PollingTimeoutDemo.kt new file mode 100644 index 000000000000..096eb0e1b20d --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/PollingTimeoutDemo.kt @@ -0,0 +1,29 @@ +/* + * 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 +} diff --git a/documentation/src/test/kotlin/example/kotlin/SystemPropertyExtensionDemo.kt b/documentation/src/test/kotlin/example/kotlin/SystemPropertyExtensionDemo.kt new file mode 100644 index 000000000000..1eb72b27f945 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/SystemPropertyExtensionDemo.kt @@ -0,0 +1,143 @@ +/* + * 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[] + + // tag::systemproperty_using_at_class_level[] + @Nested + @ClearSystemProperty(key = "some property") + inner class MySystemPropertyTest { + @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 { + // tag::systemproperty_class_restore_setup[] + @Nested + @Order(1) + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + @RestoreSystemProperties + inner class MySystemPropertyRestoreTest { + @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[] + + // tag::systemproperty_class_restore_isolated_class[] + @Nested + @Order(2) + @ReadsSystemProperty + inner class SomeOtherTestClass { + @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[] +} diff --git a/documentation/src/test/kotlin/example/kotlin/TempDirectoryDemo.kt b/documentation/src/test/kotlin/example/kotlin/TempDirectoryDemo.kt new file mode 100644 index 000000000000..925cc1401c8b --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/TempDirectoryDemo.kt @@ -0,0 +1,168 @@ +/* + * 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[] +} diff --git a/documentation/src/test/kotlin/example/kotlin/TimeoutDemo.kt b/documentation/src/test/kotlin/example/kotlin/TimeoutDemo.kt new file mode 100644 index 000000000000..5623d30115c5 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/TimeoutDemo.kt @@ -0,0 +1,41 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/defaultmethods/ComparableContract.kt b/documentation/src/test/kotlin/example/kotlin/defaultmethods/ComparableContract.kt new file mode 100644 index 000000000000..391de6f9ecfa --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/defaultmethods/ComparableContract.kt @@ -0,0 +1,41 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/defaultmethods/EqualsContract.kt b/documentation/src/test/kotlin/example/kotlin/defaultmethods/EqualsContract.kt new file mode 100644 index 000000000000..c56d36349a9c --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/defaultmethods/EqualsContract.kt @@ -0,0 +1,41 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/defaultmethods/StringTests.kt b/documentation/src/test/kotlin/example/kotlin/defaultmethods/StringTests.kt new file mode 100644 index 000000000000..917b7aa7af7d --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/defaultmethods/StringTests.kt @@ -0,0 +1,23 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/defaultmethods/Testable.kt b/documentation/src/test/kotlin/example/kotlin/defaultmethods/Testable.kt new file mode 100644 index 000000000000..9f0c064a834d --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/defaultmethods/Testable.kt @@ -0,0 +1,17 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/extensions/HttpServerExtension.kt b/documentation/src/test/kotlin/example/kotlin/extensions/HttpServerExtension.kt new file mode 100644 index 000000000000..9467d57d113e --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/extensions/HttpServerExtension.kt @@ -0,0 +1,46 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/extensions/HttpServerResource.kt b/documentation/src/test/kotlin/example/kotlin/extensions/HttpServerResource.kt new file mode 100644 index 000000000000..91405f194609 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/extensions/HttpServerResource.kt @@ -0,0 +1,48 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverConflictDemo.kt b/documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverConflictDemo.kt new file mode 100644 index 000000000000..4d894d34ea49 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverConflictDemo.kt @@ -0,0 +1,56 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverCustomAnnotationDemo.kt b/documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverCustomAnnotationDemo.kt new file mode 100644 index 000000000000..9206f63cc6f4 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverCustomAnnotationDemo.kt @@ -0,0 +1,67 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverCustomTypeDemo.kt b/documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverCustomTypeDemo.kt new file mode 100644 index 000000000000..1e0292f99e21 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverCustomTypeDemo.kt @@ -0,0 +1,60 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverNoConflictDemo.kt b/documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverNoConflictDemo.kt new file mode 100644 index 000000000000..e6885a21b664 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/extensions/ParameterResolverNoConflictDemo.kt @@ -0,0 +1,58 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/extensions/Random.kt b/documentation/src/test/kotlin/example/kotlin/extensions/Random.kt new file mode 100644 index 000000000000..d65b344a4818 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/extensions/Random.kt @@ -0,0 +1,20 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/extensions/RandomNumberDemo.kt b/documentation/src/test/kotlin/example/kotlin/extensions/RandomNumberDemo.kt new file mode 100644 index 000000000000..8dad4aa4fc3e --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/extensions/RandomNumberDemo.kt @@ -0,0 +1,49 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/extensions/RandomNumberExtension.kt b/documentation/src/test/kotlin/example/kotlin/extensions/RandomNumberExtension.kt new file mode 100644 index 000000000000..b692f81bff50 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/extensions/RandomNumberExtension.kt @@ -0,0 +1,83 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/registration/DocumentationDemo.kt b/documentation/src/test/kotlin/example/kotlin/registration/DocumentationDemo.kt new file mode 100644 index 000000000000..c955d7984106 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/registration/DocumentationDemo.kt @@ -0,0 +1,36 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/registration/DocumentationExtension.kt b/documentation/src/test/kotlin/example/kotlin/registration/DocumentationExtension.kt new file mode 100644 index 000000000000..e8a4c5a2ccc9 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/registration/DocumentationExtension.kt @@ -0,0 +1,28 @@ +/* + * 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) + } +} diff --git a/documentation/src/test/kotlin/example/kotlin/testinterface/TestInterfaceDemo.kt b/documentation/src/test/kotlin/example/kotlin/testinterface/TestInterfaceDemo.kt new file mode 100644 index 000000000000..0955a5f23fae --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/testinterface/TestInterfaceDemo.kt @@ -0,0 +1,26 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/testinterface/TestInterfaceDynamicTestsDemo.kt b/documentation/src/test/kotlin/example/kotlin/testinterface/TestInterfaceDynamicTestsDemo.kt new file mode 100644 index 000000000000..fdc673fc74c3 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/testinterface/TestInterfaceDynamicTestsDemo.kt @@ -0,0 +1,26 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/testinterface/TestLifecycleLogger.kt b/documentation/src/test/kotlin/example/kotlin/testinterface/TestLifecycleLogger.kt new file mode 100644 index 000000000000..f35be383b02b --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/testinterface/TestLifecycleLogger.kt @@ -0,0 +1,49 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/testinterface/TimeExecutionLogger.kt b/documentation/src/test/kotlin/example/kotlin/testinterface/TimeExecutionLogger.kt new file mode 100644 index 000000000000..91428a1f0d48 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/testinterface/TimeExecutionLogger.kt @@ -0,0 +1,21 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/timing/TimingExtension.kt b/documentation/src/test/kotlin/example/kotlin/timing/TimingExtension.kt new file mode 100644 index 000000000000..c3ff94843c6b --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/timing/TimingExtension.kt @@ -0,0 +1,46 @@ +/* + * 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[] diff --git a/documentation/src/test/kotlin/example/kotlin/timing/TimingExtensionTests.kt b/documentation/src/test/kotlin/example/kotlin/timing/TimingExtensionTests.kt new file mode 100644 index 000000000000..f35d4448a8bd --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/timing/TimingExtensionTests.kt @@ -0,0 +1,29 @@ +/* + * 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[]