diff --git a/documentation/modules/ROOT/pages/writing-tests/parameterized-classes-and-tests.adoc b/documentation/modules/ROOT/pages/writing-tests/parameterized-classes-and-tests.adoc index 10b0377933b8..875699141308 100644 --- a/documentation/modules/ROOT/pages/writing-tests/parameterized-classes-and-tests.adoc +++ b/documentation/modules/ROOT/pages/writing-tests/parameterized-classes-and-tests.adoc @@ -22,10 +22,26 @@ parameterized method or class, respectively. The following example demonstrates a parameterized test that uses the `@ValueSource` annotation to specify a `String` array as the source of arguments. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=first_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=first_example] +---- +-- +==== When executing the above parameterized test method, each invocation will be reported separately. For instance, the `ConsoleLauncher` will print output similar to the @@ -41,10 +57,26 @@ palindromes(String) ✔ The same `@ValueSource` annotation can be used to specify the source of arguments for a `@ParameterizedClass`. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedClassDemo.java[tags=first_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedClassDemo.kt[tags=first_example] +---- +-- +==== When executing the above parameterized test class, each invocation will be reported separately. For instance, the `ConsoleLauncher` will print output similar to the @@ -66,7 +98,7 @@ PalindromeTests ✔ [[setup]] == Required Setup -In order to use parameterized classes or tests you need to add a dependency on the +In order to use parameterized classes or tests, you need to add a dependency on the `junit-jupiter-params` artifact. Please refer to xref:appendix.adoc#dependency-metadata[Dependency Metadata] for details. [[consuming-arguments]] @@ -116,12 +148,28 @@ For constructor injection, the same rules apply as defined for above. In the following example, two arguments are injected into the constructor of the test class. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedClassDemo.java[tags=constructor_injection] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedClassDemo.kt[tags=constructor_injection] +---- +-- +==== -You may use _records_ to implement parameterized classes that avoid the boilerplate code +In Java, you can use _records_ to implement parameterized classes that avoid the boilerplate code of declaring a test class constructor. [source,java,indent=0] @@ -151,10 +199,26 @@ with `@Parameter(index)`. An _aggregator_ is any `@Parameter`-annotated field of The following example demonstrates how to use field injection to consume multiple arguments in a parameterized class. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedClassDemo.java[tags=field_injection] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedClassDemo.kt[tags=field_injection] +---- +-- +==== If field injection is used, no constructor parameters will be resolved with arguments from the source. Other xref:writing-tests/dependency-injection-for-constructors-and-methods.adoc[`ParameterResolver` extensions] @@ -165,7 +229,7 @@ may resolve constructor parameters as usual, though. `{BeforeParameterizedClassInvocation}` and `{AfterParameterizedClassInvocation}` can also be used to consume arguments if their `injectArguments` attribute is set to `true` (the -default). If so, their method signatures must follow the same rules apply as defined for +default). If so, their method signatures must follow the same rules as defined for <> and additionally use the same parameter types as the _indexed parameters_ of the parameterized test class. Please refer to the Javadoc of `{BeforeParameterizedClassInvocation}` and @@ -230,10 +294,26 @@ The following types of literal values are supported by `@ValueSource`. For example, the following `@ParameterizedTest` method will be invoked three times, with the values `1`, `2`, and `3` respectively. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ValueSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=ValueSource_example] +---- +-- +==== [[sources-null-and-empty]] === Null and Empty Sources @@ -266,18 +346,50 @@ You can also combine `@NullSource`, `@EmptySource`, and `@ValueSource` to test a range of `null`, _empty_, and _blank_ input. The following example demonstrates how to achieve this for strings. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=NullAndEmptySource_example1] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=NullAndEmptySource_example1] +---- +-- +==== Making use of the composed `@NullAndEmptySource` annotation simplifies the above as follows. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=NullAndEmptySource_example2] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=NullAndEmptySource_example2] +---- +-- +==== NOTE: Both variants of the `nullEmptyAndBlankStrings(String)` parameterized test method result in six invocations: 1 for `null`, 1 for the empty string, and 4 for the explicit @@ -288,10 +400,26 @@ blank strings supplied via `@ValueSource`. `@EnumSource` provides a convenient way to use `Enum` constants. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=EnumSource_example] +---- +-- +==== The annotation's `value` attribute is optional. When omitted, the declared type of the first parameter is used. The test will fail if it does not reference an enum type. @@ -300,18 +428,50 @@ is declared as `TemporalUnit`, i.e. the interface implemented by `ChronoUnit`, w an enum type. Changing the method parameter type to `ChronoUnit` allows you to omit the explicit enum type from the annotation as follows. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_example_autodetection] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=EnumSource_example_autodetection] +---- +-- +==== The annotation provides an optional `names` attribute that lets you specify which constants shall be used, like in the following example. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_include_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=EnumSource_include_example] +---- +-- +==== In addition to `names`, you can use the `from` and `to` attributes to specify a range of constants. The range starts from the constant specified in the `from` attribute and @@ -323,33 +483,97 @@ in the enum type, respectively. If all `names`, `from`, and `to` attributes are all constants will be used. The following example demonstrates how to specify a range of constants. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_range_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=EnumSource_range_example] +---- +-- +==== The `@EnumSource` annotation also provides an optional `mode` attribute that enables fine-grained control over which constants are passed to the test method. For example, you can exclude names from the enum constant pool or specify regular expressions as in the following examples. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_exclude_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=EnumSource_exclude_example] +---- +-- +==== +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_regex_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=EnumSource_regex_example] +---- +-- +==== You can also combine `mode` with the `from`, `to` and `names` attributes to define a range of constants while excluding specific values from that range as shown below. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=EnumSource_range_exclude_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=EnumSource_range_exclude_example] +---- +-- +==== [[sources-MethodSource]] === @MethodSource @@ -363,7 +587,7 @@ must always be `static`. Each factory method must generate a _stream_ of _arguments_, and each set of arguments within the stream will be provided as the physical arguments for individual invocations of -the annotated `@ParameterizedClass` or `@ParameterizedTest`. Generally speaking this +the annotated `@ParameterizedClass` or `@ParameterizedTest`. Generally speaking, this translates to a `Stream` of `Arguments` (i.e., `Stream`); however, the actual concrete return type can take on many forms. In this context, a "stream" is anything that JUnit can reliably convert into a `Stream`, such as `Stream`, `DoubleStream`, @@ -380,28 +604,82 @@ making it safe to use a resource such as `Files.lines()`. If you only need a single parameter, you can return a `Stream` of instances of the parameter type as demonstrated in the following example. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=simple_MethodSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tag=simple_MethodSource_example] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== For a `@ParameterizedClass`, providing a factory method name via `@MethodSource` is mandatory. For a `@ParameterizedTest`, if you do not explicitly provide a factory method name, JUnit Jupiter will search for a _factory_ method with the same name as the current `@ParameterizedTest` method by convention. This is demonstrated in the following example. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=simple_MethodSource_without_value_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tag=simple_MethodSource_without_value_example] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== Streams for primitive types (`DoubleStream`, `IntStream`, and `LongStream`) are also supported as demonstrated by the following example. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=primitive_MethodSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tag=primitive_MethodSource_example] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== If a parameterized class or test method declares multiple parameters, you need to return a collection, stream, or array of `Arguments` instances or object arrays as shown below (see @@ -410,20 +688,56 @@ the Javadoc for `{MethodSource}` for further details on supported return types). addition, `Arguments.of(Object...)` may be used as an alternative to `arguments(Object...)`. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=multi_arg_MethodSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=multi_arg_MethodSource_example] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== An external, `static` _factory_ method can be referenced by providing its _fully qualified method name_ as demonstrated in the following example. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- package example; include::example$java/example/ExternalMethodSourceDemo.java[tags=external_MethodSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +package example.kotlin + +include::example$kotlin/example/kotlin/ExternalMethodSourceDemo.kt[tags=external_MethodSource_example] +---- +-- +==== Factory methods can declare parameters, which will be provided by registered implementations of the `ParameterResolver` extension API. In the following example, the @@ -434,10 +748,28 @@ provided to differentiate them – for example, `@MethodSource("factoryMethod()" can be referenced by its fully qualified method name — for example, `@MethodSource("example.MyTests#factoryMethod(java.lang.String)")`. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/MethodSourceParameterResolutionDemo.java[tags=parameter_resolution_MethodSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/MethodSourceParameterResolutionDemo.kt[tags=parameter_resolution_MethodSource_example] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== [[sources-FieldSource]] === @FieldSource @@ -454,7 +786,7 @@ within the "stream" will be provided as the physical arguments for individual in of the annotated `@ParameterizedClass` or `@ParameterizedTest`. In this context, a "stream" is anything that JUnit can reliably convert to a `Stream`; -however, the actual concrete field type can take on many forms. Generally speaking this +however, the actual concrete field type can take on many forms. Generally speaking, this translates to a `Collection`, an `Iterable`, a `Supplier` of a stream (`Stream`, `DoubleStream`, `LongStream`, or `IntStream`), a `Supplier` of an `Iterator`, an array of objects or primitives, or any type that provides an `iterator(): Iterator` method (such @@ -490,19 +822,55 @@ search in the test class for a field that has the same name as the current This parameterized test method will be invoked twice: with the values `"apple"` and `"banana"`. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=default_field_FieldSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=default_field_FieldSource_example] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== The following example demonstrates how to provide a single explicit field name via `@FieldSource`. This parameterized test method will be invoked twice: with the values `"apple"` and `"banana"`. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=explicit_field_FieldSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tag=explicit_field_FieldSource_example] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== The following example demonstrates how to provide multiple explicit field names via `@FieldSource`. This example uses the `listOfFruits` field from the previous example as @@ -510,10 +878,28 @@ well as the `additionalFruits` field. Consequently, this parameterized test meth be invoked four times: with the values `"apple"`, `"banana"`, `"cherry"`, and `"dewberry"`. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=multiple_fields_FieldSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tag=multiple_fields_FieldSource_example] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== It is also possible to provide a `Stream`, `DoubleStream`, `IntStream`, `LongStream`, or `Iterator` as the source of arguments via a `@FieldSource` field as long as the stream or @@ -522,10 +908,28 @@ how to provide a `Supplier` of a `Stream` of named arguments. This parameterized method will be invoked twice: with the values `"apple"` and `"banana"` and with display names `"Apple"` and `"Banana"`, respectively. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=named_arguments_FieldSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tag=named_arguments_FieldSource_example] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== [NOTE] ==== @@ -541,10 +945,28 @@ If a parameterized class or test method declares multiple parameters, the corres `Arguments` instances or object arrays as shown below (see the Javadoc for `{FieldSource}` for further details on supported types). +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=multi_arg_FieldSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tag=multi_arg_FieldSource_example] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== [NOTE] ==== @@ -555,10 +977,26 @@ Note that `arguments(Object...)` is a static factory method defined in the An external, `static` `@FieldSource` field can be referenced by providing its _fully qualified field name_ as demonstrated in the following example. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ExternalFieldSourceDemo.java[tags=external_field_FieldSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ExternalFieldSourceDemo.kt[tags=external_field_FieldSource_example] +---- +-- +==== [[sources-CsvSource]] === @CsvSource @@ -569,10 +1007,26 @@ represents a CSV record and results in one invocation of the parameterized class test. The first record may optionally be used to supply CSV headers (see the Javadoc for the `useHeadersInDisplayName` attribute for details and an example). +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=CsvSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=CsvSource_example] +---- +-- +==== The default delimiter is a comma (`,`), but you can use another character by setting the `delimiter` attribute. Alternatively, the `delimiterString` attribute allows you to use a @@ -616,6 +1070,11 @@ example below. Using a text block, the previous example can be implemented as follows. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- @ParameterizedTest @@ -630,6 +1089,27 @@ void testWithCsvSource(String fruit, int rank) { // ... } ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +@ParameterizedTest +@CsvSource(useHeadersInDisplayName = true, textBlock = """ + FRUIT, RANK + apple, 1 + banana, 2 + 'lemon, lime', 0xF1 + strawberry, 700_000 + """) +fun testWithCsvSource(fruit: String, rank: Int) { + // ... +} +---- +-- +==== The generated display names for the previous example include the CSV header names. @@ -652,6 +1132,11 @@ be placed either at the end of the last line of input or on the following line, left aligned with the rest of the input (as can be seen in the example below which demonstrates formatting similar to a table). +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- @ParameterizedTest @@ -672,12 +1157,39 @@ void testWithCsvSource(String fruit, int rank) { // ... } ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +@ParameterizedTest +@CsvSource(delimiter = '|', quoteCharacter = '"', textBlock = """ + #----------------------------- + # FRUIT | RANK + #----------------------------- + apple | 1 + #----------------------------- + banana | 2 + #----------------------------- + "lemon lime" | 0xF1 + #----------------------------- + strawberry | 700_000 + #----------------------------- + """) +fun testWithCsvSource(fruit: String, rank: Int) { + // ... +} +---- +-- +==== [NOTE] ==== Java's https://docs.oracle.com/en/java/javase/17/text-blocks/index.html[text block] feature automatically removes _incidental whitespace_ when the code is compiled. -However other JVM languages such as Groovy and Kotlin do not. Thus, if you are using a +However, other JVM languages such as Groovy and Kotlin do not. Thus, if you are using a programming language other than Java and your text block contains comments or new lines within quoted strings, you will need to ensure that there is no leading whitespace within your text block. @@ -703,10 +1215,26 @@ cannot be set simultaneously. NOTE: Any line beginning with the value of the `commentCharacter` attribute (`+++#+++` by default) will be interpreted as a comment and will be ignored. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=CsvFileSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=CsvFileSource_example] +---- +-- +==== [source,csv,indent=0] .two-column.csv @@ -757,15 +1285,47 @@ by default. This behavior can be changed by setting the that an implementation of `ArgumentsProvider` must be declared as either a top-level class or as a `static` nested class. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsSource_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=ArgumentsSource_example] +---- +-- +==== +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsProvider_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=ArgumentsProvider_example] +---- +-- +==== If you wish to implement a custom `ArgumentsProvider` that also consumes an annotation (like built-in providers such as `{ValueArgumentsProvider}` or `{CsvArgumentsProvider}`), @@ -775,10 +1335,26 @@ Moreover, `ArgumentsProvider` implementations may declare constructor parameters they need to be resolved by a registered `ParameterResolver` as demonstrated in the following example. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsProviderWithConstructorInjection_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=ArgumentsProviderWithConstructorInjection_example] +---- +-- +==== [[repeatable-sources]] === Multiple sources using repeatable annotations @@ -786,10 +1362,28 @@ include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsProviderW Repeatable annotations provide a convenient way to specify multiple sources from different providers. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=repeatable_annotations] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tag=repeatable_annotations] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== Following the above parameterized test, a test case will run for each argument: @@ -826,10 +1420,26 @@ To change this behavior for a single parameterized class or test method, use the `argumentCountValidation` attribute of the `@ParameterizedClass` or `@ParameterizedTest` annotation: +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=argument_count_validation] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=argument_count_validation] +---- +-- +==== [[argument-conversion]] == Argument Conversion @@ -855,10 +1465,26 @@ For example, if a `@ParameterizedClass` or `@ParameterizedTest` declares a param of type `TimeUnit` and the actual type supplied by the declared source is a `String`, the string will be automatically converted into the corresponding `TimeUnit` enum constant. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=implicit_conversion_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=implicit_conversion_example] +---- +-- +==== `String` instances are implicitly converted to the following target types. @@ -920,7 +1546,7 @@ or _factory constructor_ as defined below. accepts either a single `String` argument or a single `CharSequence` argument and returns an instance of the target type. The name of the method can be arbitrary and need not follow any particular convention. -- __factory constructor__: a non-private constructor in the target type that accepts a +- __factory constructor__: a non-private constructor in the target type that accepts either a single `String` argument or a single `CharSequence` argument. Note that the target type must be declared as either a top-level class or as a `static` nested class. @@ -940,15 +1566,47 @@ For example, in the following `@ParameterizedTest` method, the `Book` argument w created by invoking the `Book.fromTitle(String)` factory method and passing `"42 Cats"` as the title of the book. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=implicit_fallback_conversion_example] ---- +-- +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=implicit_fallback_conversion_example] +---- +-- +==== + +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=implicit_fallback_conversion_example_Book] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=implicit_fallback_conversion_example_Book] +---- +-- +==== [[argument-conversion-explicit]] === Explicit Conversion @@ -958,33 +1616,97 @@ Instead of relying on implicit argument conversion, you may explicitly specify a like in the following example. Note that an implementation of `ArgumentConverter` must be declared as either a top-level class or as a `static` nested class. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=explicit_conversion_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=explicit_conversion_example] +---- +-- +==== +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=explicit_conversion_example_ToStringArgumentConverter] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=explicit_conversion_example_ToStringArgumentConverter] +---- +-- +==== If the converter is only meant to convert one type to another, you can extend `TypedArgumentConverter` to avoid boilerplate type checks. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=explicit_conversion_example_TypedArgumentConverter] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=explicit_conversion_example_TypedArgumentConverter] +---- +-- +==== Explicit argument converters are meant to be implemented by test and extension authors. Thus, `junit-jupiter-params` only provides a single explicit argument converter that may also serve as a reference implementation: `JavaTimeArgumentConverter`. It is used via the composed annotation `JavaTimeConversionPattern`. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=explicit_java_time_converter] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=explicit_java_time_converter] +---- +-- +==== If you wish to implement a custom `{ArgumentConverter}` that also consumes an annotation (like `JavaTimeArgumentConverter`), you have the possibility to extend the @@ -1006,10 +1728,26 @@ test method. In addition, type conversion is supported as discussed in Besides, you can retrieve the current test invocation index with `ArgumentsAccessor.getInvocationIndex()`. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAccessor_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=ArgumentsAccessor_example] +---- +-- +==== _An instance of `ArgumentsAccessor` is automatically injected into any parameter of type `ArgumentsAccessor`._ @@ -1028,15 +1766,47 @@ provided as an argument for the corresponding parameter when the parameterized t invoked. Note that an implementation of `ArgumentsAggregator` must be declared as either a top-level class or as a `static` nested class. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_example] ---- +-- +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=ArgumentsAggregator_example] +---- +-- +==== + +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_example_PersonAggregator] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=ArgumentsAggregator_example_PersonAggregator] +---- +-- +==== If you find yourself repeatedly declaring `@AggregateWith(MyTypeAggregator.class)` for multiple parameterized classes or methods across your codebase, you may wish to create a @@ -1044,15 +1814,47 @@ custom _composed annotation_ such as `@CsvToMyType` that is meta-annotated with `@AggregateWith(MyTypeAggregator.class)`. The following example demonstrates this in action with a custom `@CsvToPerson` annotation. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_with_custom_annotation_example] ---- +-- +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=ArgumentsAggregator_with_custom_annotation_example] +---- +-- +==== + +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_with_custom_annotation_example_CsvToPerson] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=ArgumentsAggregator_with_custom_annotation_example_CsvToPerson] +---- +-- +==== [[display-names]] @@ -1075,11 +1877,27 @@ for Kotlin. However, you can customize invocation display names via the `name` attribute of the `@ParameterizedClass` or `@ParameterizedTest` annotation as in the following example. -====== +======== +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=custom_display_names] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=custom_display_names] +---- +-- +==== When executing the above method using the `ConsoleLauncher` you will see output similar to the following. @@ -1090,7 +1908,7 @@ Display name of container ✔ ├─ 2 ==> the rank of "banana" is "2" ✔ └─ 3 ==> the rank of "lemon, lime" is "3" ✔ .... -====== +======== [NOTE] ==== @@ -1125,11 +1943,29 @@ Use the `{Named}` API to provide a custom name for an individual argument, and t name will be used if the argument is included in the invocation display name, like in the example below. -====== +======== +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=named_arguments] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tag=named_arguments] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== When executing the above method using the `ConsoleLauncher` you will see output similar to the following. @@ -1139,7 +1975,7 @@ A parameterized test with named arguments ✔ ├─ 1: An important file ✔ └─ 2: Another file ✔ .... -====== +======== [NOTE] ==== @@ -1153,11 +1989,29 @@ Similarly, `named(String, Object)` is a static factory method defined in the Use the `ArgumentSet` API to provide a custom name for the entire set of arguments, and the custom name will be used as the display name, like in the example below. -====== +======== +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=named_argument_set] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tag=named_argument_set] +---- + +NOTE: For conciseness, this snippet assumes that the enclosing test class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== When executing the above method using the `ConsoleLauncher` you will see output similar to the following. @@ -1167,7 +2021,7 @@ A parameterized test with named argument sets ✔ ├─ [1] Important files ✔ └─ [2] Other files ✔ .... -====== +======== [NOTE] ==== @@ -1275,10 +2129,26 @@ parameter list. Since a test class may contain regular tests as well as paramete tests with different parameter lists, values from argument sources are not resolved for lifecycle methods (for example, `@BeforeEach`) and test class constructors. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/ParameterizedTestDemo.java[tags=ParameterResolver_example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedTestDemo.kt[tags=ParameterResolver_example] +---- +-- +==== [[lifecycle-interop-classes]] === Parameterized Classes @@ -1309,11 +2179,29 @@ arguments of the parameterized class (see the Javadoc of details). This may, for example, be used to initialize the used arguments as demonstrated by the following example. -[source,java,indent=0] .Using parameterized class lifecycle methods +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] ---- include::example$java/example/ParameterizedLifecycleDemo.java[tags=example] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ParameterizedLifecycleDemo.kt[tags=example] +---- + +NOTE: For conciseness, this snippet uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== <1> Initialization of the argument _before_ each invocation of the parameterized class <2> Usage of the previously initialized argument in a test method <3> Validation and cleanup of the argument _after_ each invocation of the parameterized diff --git a/documentation/modules/ROOT/pages/writing-tests/tagging-and-filtering.adoc b/documentation/modules/ROOT/pages/writing-tests/tagging-and-filtering.adoc index ad5bb6c07300..5e7f6f2a07f5 100644 --- a/documentation/modules/ROOT/pages/writing-tests/tagging-and-filtering.adoc +++ b/documentation/modules/ROOT/pages/writing-tests/tagging-and-filtering.adoc @@ -5,10 +5,26 @@ used to filter xref:running-tests/intro.adoc[test discovery and execution]. Plea xref:running-tests/tags.adoc[] section for more information about tag support in the JUnit Platform. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/TaggingDemo.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/TaggingDemo.kt[tags=user_guide] +---- +-- +==== TIP: See xref:writing-tests/annotations.adoc#annotations[Meta-Annotations and Composed Annotations] for examples demonstrating how to create custom annotations for tags. diff --git a/documentation/modules/ROOT/pages/writing-tests/test-execution-order.adoc b/documentation/modules/ROOT/pages/writing-tests/test-execution-order.adoc index 20bae4e323fb..ef292c51e34e 100644 --- a/documentation/modules/ROOT/pages/writing-tests/test-execution-order.adoc +++ b/documentation/modules/ROOT/pages/writing-tests/test-execution-order.adoc @@ -41,10 +41,26 @@ NOTE: See also: xref:extensions/relative-execution-order-of-user-code-and-extens The following example demonstrates how to guarantee that test methods are executed in the order specified via the `@Order` annotation. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/OrderedTestsDemo.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/OrderedTestsDemo.kt[tags=user_guide] +---- +-- +==== [[methods-default]] === Setting the Default Method Orderer @@ -132,7 +148,23 @@ Note that a local `@TestClassOrder` declaration always overrides an inherited The following example demonstrates how to guarantee that `@Nested` test classes are executed in the order specified via the `@Order` annotation. +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] ---- include::example$java/example/OrderedNestedTestClassesDemo.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/OrderedNestedTestClassesDemo.kt[tags=user_guide] +---- +-- +==== diff --git a/documentation/src/test/kotlin/example/kotlin/ExternalFieldSourceDemo.kt b/documentation/src/test/kotlin/example/kotlin/ExternalFieldSourceDemo.kt new file mode 100644 index 000000000000..810a1785883b --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/ExternalFieldSourceDemo.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.params.ParameterizedTest +import org.junit.jupiter.params.provider.FieldSource + +class ExternalFieldSourceDemo { + // tag::external_field_FieldSource_example[] + @ParameterizedTest + @FieldSource("example.kotlin.FruitUtils#tropicalFruits") + fun testWithExternalFieldSource(tropicalFruit: String) { + // test with tropicalFruit + } + // end::external_field_FieldSource_example[] +} + +object FruitUtils { + @JvmField + val tropicalFruits: List = listOf("pineapple", "kiwi") +} diff --git a/documentation/src/test/kotlin/example/kotlin/ExternalMethodSourceDemo.kt b/documentation/src/test/kotlin/example/kotlin/ExternalMethodSourceDemo.kt new file mode 100644 index 000000000000..da0b739a301f --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/ExternalMethodSourceDemo.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 + +// tag::external_MethodSource_example[] +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource + +class ExternalMethodSourceDemo { + @ParameterizedTest + @MethodSource("example.kotlin.StringsProviders#tinyStrings") + fun testWithExternalMethodSource(tinyString: String) { + // test with tiny string + } +} + +object StringsProviders { + @JvmStatic + fun tinyStrings() = sequenceOf(".", "oo", "OOO") +} +// end::external_MethodSource_example[] diff --git a/documentation/src/test/kotlin/example/kotlin/MethodSourceParameterResolutionDemo.kt b/documentation/src/test/kotlin/example/kotlin/MethodSourceParameterResolutionDemo.kt new file mode 100644 index 000000000000..dd9a7c49088b --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/MethodSourceParameterResolutionDemo.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2015-2026 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.kotlin + +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.ParameterContext +import org.junit.jupiter.api.extension.ParameterResolver +import org.junit.jupiter.api.extension.RegisterExtension +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.MethodSource + +@TestInstance(PER_CLASS) +class MethodSourceParameterResolutionDemo { + // tag::parameter_resolution_MethodSource_example[] + @JvmField + @RegisterExtension + val integerResolver = IntegerResolver() + + @ParameterizedTest + @MethodSource("factoryMethodWithArguments") + fun testWithFactoryMethodWithArguments(argument: String) { + assertTrue(argument.startsWith("2")) + } + + fun factoryMethodWithArguments(quantity: Int) = + sequenceOf( + arguments("$quantity apples"), + arguments("$quantity lemons") + ) + + class IntegerResolver : 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::parameter_resolution_MethodSource_example[] +} diff --git a/documentation/src/test/kotlin/example/kotlin/OrderedNestedTestClassesDemo.kt b/documentation/src/test/kotlin/example/kotlin/OrderedNestedTestClassesDemo.kt new file mode 100644 index 000000000000..8552e8d6b87a --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/OrderedNestedTestClassesDemo.kt @@ -0,0 +1,38 @@ +/* + * 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.ClassOrderer +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestClassOrder + +@TestClassOrder(ClassOrderer.OrderAnnotation::class) +class OrderedNestedTestClassesDemo { + @Nested + @Order(1) + inner class PrimaryTests { + @Test + fun test1() { + } + } + + @Nested + @Order(2) + inner class SecondaryTests { + @Test + fun test2() { + } + } +} +// end::user_guide[] diff --git a/documentation/src/test/kotlin/example/kotlin/OrderedTestsDemo.kt b/documentation/src/test/kotlin/example/kotlin/OrderedTestsDemo.kt new file mode 100644 index 000000000000..a0fa88d81268 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/OrderedTestsDemo.kt @@ -0,0 +1,39 @@ +/* + * 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.MethodOrderer.OrderAnnotation +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestMethodOrder + +@TestMethodOrder(OrderAnnotation::class) +class OrderedTestsDemo { + @Test + @Order(1) + fun nullValues() { + // perform assertions against null values + } + + @Test + @Order(2) + fun emptyValues() { + // perform assertions against empty values + } + + @Test + @Order(3) + fun validValues() { + // perform assertions against valid values + } +} +// end::user_guide[] diff --git a/documentation/src/test/kotlin/example/kotlin/ParameterizedClassDemo.kt b/documentation/src/test/kotlin/example/kotlin/ParameterizedClassDemo.kt index d1d9580c3567..15d582fd1560 100644 --- a/documentation/src/test/kotlin/example/kotlin/ParameterizedClassDemo.kt +++ b/documentation/src/test/kotlin/example/kotlin/ParameterizedClassDemo.kt @@ -10,18 +10,114 @@ package example.kotlin +import example.util.StringUtils import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD import org.junit.jupiter.params.Parameter import org.junit.jupiter.params.ParameterizedClass import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource import org.junit.jupiter.params.provider.ValueSource import java.time.Duration class ParameterizedClassDemo { + // @formatter:off + @Nested + // tag::first_example[] + @ParameterizedClass + @ValueSource(strings = ["racecar", "radar", "able was I ere I saw elba"]) + // end::first_example[] + inner + // tag::first_example[] + class PalindromeTests { + // end::first_example[] + // @formatter:on + // tag::first_example[] + @Parameter + lateinit var candidate: String + + @Test + fun palindrome() { + assertTrue(StringUtils.isPalindrome(candidate)) + } + + @Test + fun reversePalindrome() { + val reverseCandidate = candidate.reversed() + assertTrue(StringUtils.isPalindrome(reverseCandidate)) + } + } + // end::first_example[] + + @Nested + inner class ConstructorInjection { + // @formatter:off + @Nested + // tag::constructor_injection[] + @ParameterizedClass + @CsvSource("apple, 23", "banana, 42") + // end::constructor_injection[] + inner + // tag::constructor_injection[] + class FruitTests( + private val fruit: String, + private val quantity: Int + ) { + // end::constructor_injection[] + // @formatter:on + // tag::constructor_injection[] + @Test + fun test() { + assertFruit(fruit) + assertQuantity(quantity) + } + + @Test + fun anotherTest() { + // ... + } + } + // end::constructor_injection[] + } + + @Nested + inner class FieldInjection { + // @formatter:off + @Nested + // tag::field_injection[] + @ParameterizedClass + @CsvSource("apple, 23", "banana, 42") + // end::field_injection[] + inner + // tag::field_injection[] + class FruitTests { + // end::field_injection[] + // @formatter:on + // tag::field_injection[] + @Parameter(0) + lateinit var fruit: String + + @Parameter(1) + var quantity: Int = 0 + + @Test + fun test() { + assertFruit(fruit) + assertQuantity(quantity) + } + + @Test + fun anotherTest() { + // ... + } + } + // end::field_injection[] + } + // @formatter:off @Nested // tag::nested[] @@ -58,7 +154,7 @@ class ParameterizedClassDemo { private fun assertFruit(fruit: String) { assertTrue( - listOf("apple", "banana", "cherry", "dewberry").contains(fruit) + fruit in listOf("apple", "banana", "cherry", "dewberry") ) { "not a fruit: $fruit" } } diff --git a/documentation/src/test/kotlin/example/kotlin/ParameterizedLifecycleDemo.kt b/documentation/src/test/kotlin/example/kotlin/ParameterizedLifecycleDemo.kt new file mode 100644 index 000000000000..8230272db63a --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/ParameterizedLifecycleDemo.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2026 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.kotlin + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import org.junit.jupiter.api.io.TempDir +import org.junit.jupiter.params.AfterParameterizedClassInvocation +import org.junit.jupiter.params.BeforeParameterizedClassInvocation +import org.junit.jupiter.params.Parameter +import org.junit.jupiter.params.ParameterizedClass +import org.junit.jupiter.params.provider.MethodSource +import java.nio.file.Files +import java.nio.file.Path + +// tag::example[] +@ParameterizedClass +@MethodSource("textFiles") +@TestInstance(PER_CLASS) +class TextFileTests { + @Parameter + lateinit var textFile: TextFile + + fun textFiles(): List = + listOf( + TextFile("file1", "first content"), + TextFile("file2", "second content") + ) + + @BeforeParameterizedClassInvocation + fun beforeInvocation( + textFile: TextFile, + @TempDir tempDir: Path + ) { // <1> + val filePath = tempDir.resolve(textFile.fileName) + textFile.path = Files.writeString(filePath, textFile.content) + } + + @AfterParameterizedClassInvocation + fun afterInvocation(textFile: TextFile) { // <3> + val actualContent = Files.readString(textFile.path) + assertEquals(textFile.content, actualContent, "Content must not have changed") + // Custom cleanup logic, if necessary + // File will be deleted automatically by @TempDir support + } + + @Test + fun test() { + assertTrue(Files.exists(textFile.path)) // <2> + } + + @Test + fun anotherTest() { + // ... + } +} + +class TextFile( + val fileName: String, + val content: String +) { + lateinit var path: Path + + override fun toString(): String = fileName +} +// end::example[] diff --git a/documentation/src/test/kotlin/example/kotlin/ParameterizedTestDemo.kt b/documentation/src/test/kotlin/example/kotlin/ParameterizedTestDemo.kt new file mode 100644 index 000000000000..dffe53343343 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/ParameterizedTestDemo.kt @@ -0,0 +1,606 @@ +/* + * 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.domain.Person +import example.domain.Person.Gender +import example.util.StringUtils +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Named.named +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.TestInfo +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import org.junit.jupiter.api.TestReporter +import org.junit.jupiter.api.extension.AnnotatedElementContext +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.parallel.Execution +import org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD +import org.junit.jupiter.params.ArgumentCountValidationMode +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.aggregator.AggregateWith +import org.junit.jupiter.params.aggregator.ArgumentsAccessor +import org.junit.jupiter.params.aggregator.SimpleArgumentsAggregator +import org.junit.jupiter.params.converter.ConvertWith +import org.junit.jupiter.params.converter.JavaTimeConversionPattern +import org.junit.jupiter.params.converter.SimpleArgumentConverter +import org.junit.jupiter.params.converter.TypedArgumentConverter +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import org.junit.jupiter.params.provider.ArgumentsSource +import org.junit.jupiter.params.provider.CsvFileSource +import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.EmptySource +import org.junit.jupiter.params.provider.EnumSource +import org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE +import org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL +import org.junit.jupiter.params.provider.FieldSource +import org.junit.jupiter.params.provider.MethodSource +import org.junit.jupiter.params.provider.NullAndEmptySource +import org.junit.jupiter.params.provider.NullSource +import org.junit.jupiter.params.provider.ValueSource +import org.junit.jupiter.params.support.ParameterDeclarations +import java.io.File +import java.time.LocalDate +import java.time.temporal.ChronoUnit +import java.time.temporal.TemporalUnit +import java.util.EnumSet +import java.util.function.Supplier +import java.util.stream.Stream + +@Execution(SAME_THREAD) +@TestInstance(PER_CLASS) +class ParameterizedTestDemo { + @BeforeEach + fun printDisplayName(testInfo: TestInfo) { + println(testInfo.displayName) + } + + // tag::first_example[] + @ParameterizedTest + @ValueSource(strings = ["racecar", "radar", "able was I ere I saw elba"]) + fun palindromes(candidate: String) { + assertTrue(StringUtils.isPalindrome(candidate)) + } + // end::first_example[] + + // tag::ValueSource_example[] + @ParameterizedTest + @ValueSource(ints = [1, 2, 3]) + fun testWithValueSource(argument: Int) { + assertTrue(argument in 1..<4) + } + // end::ValueSource_example[] + + @Nested + inner class NullAndEmptySource1 { + // tag::NullAndEmptySource_example1[] + @ParameterizedTest + @NullSource + @EmptySource + @ValueSource(strings = [" ", " ", "\t", "\n"]) + fun nullEmptyAndBlankStrings(text: String?) { + assertTrue(text.isNullOrBlank()) + } + // end::NullAndEmptySource_example1[] + } + + @Nested + inner class NullAndEmptySource2 { + // tag::NullAndEmptySource_example2[] + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = [" ", " ", "\t", "\n"]) + fun nullEmptyAndBlankStrings(text: String?) { + assertTrue(text.isNullOrBlank()) + } + // end::NullAndEmptySource_example2[] + } + + // tag::EnumSource_example[] + @ParameterizedTest + @EnumSource(ChronoUnit::class) + fun testWithEnumSource(unit: TemporalUnit) { + assertNotNull(unit) + } + // end::EnumSource_example[] + + // tag::EnumSource_example_autodetection[] + @ParameterizedTest + @EnumSource + fun testWithEnumSourceWithAutoDetection(unit: ChronoUnit) { + assertNotNull(unit) + } + // end::EnumSource_example_autodetection[] + + // tag::EnumSource_include_example[] + @ParameterizedTest + @EnumSource(names = ["DAYS", "HOURS"]) + fun testWithEnumSourceInclude(unit: ChronoUnit) { + assertTrue(unit in EnumSet.of(ChronoUnit.DAYS, ChronoUnit.HOURS)) + } + // end::EnumSource_include_example[] + + // tag::EnumSource_range_example[] + @ParameterizedTest + @EnumSource(from = "HOURS", to = "DAYS") + fun testWithEnumSourceRange(unit: ChronoUnit) { + assertTrue(unit in EnumSet.of(ChronoUnit.HOURS, ChronoUnit.HALF_DAYS, ChronoUnit.DAYS)) + } + // end::EnumSource_range_example[] + + // tag::EnumSource_exclude_example[] + @ParameterizedTest + @EnumSource(mode = EXCLUDE, names = ["ERAS", "FOREVER"]) + fun testWithEnumSourceExclude(unit: ChronoUnit) { + assertFalse(unit in EnumSet.of(ChronoUnit.ERAS, ChronoUnit.FOREVER)) + } + // end::EnumSource_exclude_example[] + + // tag::EnumSource_regex_example[] + @ParameterizedTest + @EnumSource(mode = MATCH_ALL, names = ["^.*DAYS$"]) + fun testWithEnumSourceRegex(unit: ChronoUnit) { + assertTrue(unit.name.endsWith("DAYS")) + } + // end::EnumSource_regex_example[] + + // tag::EnumSource_range_exclude_example[] + @ParameterizedTest + @EnumSource(from = "HOURS", to = "DAYS", mode = EXCLUDE, names = ["HALF_DAYS"]) + fun testWithEnumSourceRangeExclude(unit: ChronoUnit) { + assertTrue(unit in EnumSet.of(ChronoUnit.HOURS, ChronoUnit.DAYS)) + assertFalse(unit in EnumSet.of(ChronoUnit.HALF_DAYS)) + } + // end::EnumSource_range_exclude_example[] + + // tag::simple_MethodSource_example[] + @ParameterizedTest + @MethodSource("stringProvider") + fun testWithExplicitLocalMethodSource(argument: String) { + assertNotNull(argument) + } + + fun stringProvider() = sequenceOf("apple", "banana") + // end::simple_MethodSource_example[] + + // tag::simple_MethodSource_without_value_example[] + @ParameterizedTest + @MethodSource + fun testWithDefaultLocalMethodSource(argument: String) { + assertNotNull(argument) + } + + fun testWithDefaultLocalMethodSource() = sequenceOf("apple", "banana") + // end::simple_MethodSource_without_value_example[] + + // tag::primitive_MethodSource_example[] + @ParameterizedTest + @MethodSource("range") + fun testWithRangeMethodSource(argument: Int) { + assertNotEquals(9, argument) + } + + fun range() = 10..<20 + // end::primitive_MethodSource_example[] + + // tag::multi_arg_MethodSource_example[] + @ParameterizedTest + @MethodSource("stringIntAndListProvider") + fun testWithMultiArgMethodSource( + str: String, + num: Int, + list: List + ) { + assertEquals(5, str.length) + assertTrue(num in 1..2) + assertEquals(2, list.size) + } + + fun stringIntAndListProvider() = + sequenceOf( + arguments("apple", 1, listOf("a", "b")), + arguments("lemon", 2, listOf("x", "y")) + ) + // end::multi_arg_MethodSource_example[] + + // tag::default_field_FieldSource_example[] + @ParameterizedTest + @FieldSource + fun arrayOfFruits(fruit: String) { + assertFruit(fruit) + } + + val arrayOfFruits = arrayOf("apple", "banana") + // end::default_field_FieldSource_example[] + + // tag::explicit_field_FieldSource_example[] + @ParameterizedTest + @FieldSource("listOfFruits") + fun singleFieldSource(fruit: String) { + assertFruit(fruit) + } + + val listOfFruits = listOf("apple", "banana") + // end::explicit_field_FieldSource_example[] + + // tag::multiple_fields_FieldSource_example[] + @ParameterizedTest + @FieldSource("listOfFruits", "additionalFruits") + fun multipleFieldSources(fruit: String) { + assertFruit(fruit) + } + + val additionalFruits = listOf("cherry", "dewberry") + // end::multiple_fields_FieldSource_example[] + + // tag::named_arguments_FieldSource_example[] + @ParameterizedTest + @FieldSource + fun namedArgumentsSupplier(fruit: String) { + assertFruit(fruit) + } + + val namedArgumentsSupplier: Supplier> = + Supplier { + Stream.of( + arguments(named("Apple", "apple")), + arguments(named("Banana", "banana")) + ) + } + // end::named_arguments_FieldSource_example[] + + private fun assertFruit(fruit: String) { + assertTrue(fruit in listOf("apple", "banana", "cherry", "dewberry")) + } + + // tag::multi_arg_FieldSource_example[] + @ParameterizedTest + @FieldSource("stringIntAndListArguments") + fun testWithMultiArgFieldSource( + str: String, + num: Int, + list: List + ) { + assertEquals(5, str.length) + assertTrue(num in 1..2) + assertEquals(2, list.size) + } + + var stringIntAndListArguments = + listOf( + arguments("apple", 1, listOf("a", "b")), + arguments("lemon", 2, listOf("x", "y")) + ) + // end::multi_arg_FieldSource_example[] + + // tag::CsvSource_example[] + @ParameterizedTest + @CsvSource( + "apple, 1", + "banana, 2", + "'lemon, lime', 0xF1", + "strawberry, 700_000" + ) + fun testWithCsvSource( + fruit: String, + rank: Int + ) { + assertNotNull(fruit) + assertNotEquals(0, rank) + } + // end::CsvSource_example[] + + // tag::CsvFileSource_example[] + @ParameterizedTest + @CsvFileSource(resources = ["/two-column.csv"], numLinesToSkip = 1) + fun testWithCsvFileSourceFromClasspath( + country: String, + reference: Int + ) { + assertNotNull(country) + assertNotEquals(0, reference) + } + + @ParameterizedTest + @CsvFileSource(files = ["src/test/resources/two-column.csv"], numLinesToSkip = 1) + fun testWithCsvFileSourceFromFile( + country: String, + reference: Int + ) { + assertNotNull(country) + assertNotEquals(0, reference) + } + + @ParameterizedTest + @CsvFileSource(resources = ["/two-column.csv"], useHeadersInDisplayName = true) + fun testWithCsvFileSourceAndHeaders( + country: String, + reference: Int + ) { + assertNotNull(country) + assertNotEquals(0, reference) + } + // end::CsvFileSource_example[] + + // tag::ArgumentsSource_example[] + @ParameterizedTest + @ArgumentsSource(MyArgumentsProvider::class) + fun testWithArgumentsSource(argument: String) { + assertNotNull(argument) + } + + // end::ArgumentsSource_example[] + // tag::ArgumentsProvider_example[] + class MyArgumentsProvider : ArgumentsProvider { + override fun provideArguments( + parameters: ParameterDeclarations, + context: ExtensionContext + ): Stream = Stream.of("apple", "banana").map(Arguments::of) + } + // end::ArgumentsProvider_example[] + + @ParameterizedTest + @ArgumentsSource(MyArgumentsProviderWithConstructorInjection::class) + fun testWithArgumentsSourceWithConstructorInjection(argument: String) { + assertNotNull(argument) + } + + // tag::ArgumentsProviderWithConstructorInjection_example[] + class MyArgumentsProviderWithConstructorInjection( + private val testInfo: TestInfo + ) : ArgumentsProvider { + override fun provideArguments( + parameters: ParameterDeclarations, + context: ExtensionContext + ): Stream = Stream.of(arguments(testInfo.displayName)) + } + // end::ArgumentsProviderWithConstructorInjection_example[] + + // tag::ParameterResolver_example[] + @BeforeEach + fun beforeEach(testInfo: TestInfo) { + // ... + } + + @ParameterizedTest + @ValueSource(strings = ["apple"]) + fun testWithRegularParameterResolver( + argument: String, + testReporter: TestReporter + ) { + testReporter.publishEntry("argument", argument) + } + + @AfterEach + fun afterEach(testInfo: TestInfo) { + // ... + } + // end::ParameterResolver_example[] + + // tag::implicit_conversion_example[] + @ParameterizedTest + @ValueSource(strings = ["SECONDS"]) + fun testWithImplicitArgumentConversion(argument: ChronoUnit) { + assertNotNull(argument.name) + } + // end::implicit_conversion_example[] + + // tag::implicit_fallback_conversion_example[] + @ParameterizedTest + @ValueSource(strings = ["42 Cats"]) + fun testWithImplicitFallbackArgumentConversion(book: Book) { + assertEquals("42 Cats", book.title) + } + + // end::implicit_fallback_conversion_example[] + // tag::implicit_fallback_conversion_example_Book[] + class Book private constructor( + val title: String + ) { + companion object { + @JvmStatic + fun fromTitle(title: String): Book = Book(title) + } + } + // end::implicit_fallback_conversion_example_Book[] + + // tag::explicit_conversion_example[] + @ParameterizedTest + @EnumSource(ChronoUnit::class) + fun testWithExplicitArgumentConversion( + @ConvertWith(ToStringArgumentConverter::class) argument: String + ) { + assertNotNull(ChronoUnit.valueOf(argument)) + } + + // end::explicit_conversion_example[] + // tag::explicit_conversion_example_ToStringArgumentConverter[] + class ToStringArgumentConverter : SimpleArgumentConverter() { + override fun convert( + source: Any?, + targetType: Class<*> + ): Any { + assertEquals(String::class.java, targetType, "Can only convert to String") + return if (source is Enum<*>) source.name else source.toString() + } + } + // end::explicit_conversion_example_ToStringArgumentConverter[] + + // tag::explicit_conversion_example_TypedArgumentConverter[] + class ToLengthArgumentConverter : + TypedArgumentConverter( + String::class.java, + Int::class.javaObjectType + ) { + override fun convert(source: String?): Int = source?.length ?: 0 + } + // end::explicit_conversion_example_TypedArgumentConverter[] + + // tag::explicit_java_time_converter[] + @ParameterizedTest + @ValueSource(strings = ["01.01.2017", "31.12.2017"]) + fun testWithExplicitJavaTimeConverter( + @JavaTimeConversionPattern("dd.MM.yyyy") argument: LocalDate + ) { + assertEquals(2017, argument.year) + } + // end::explicit_java_time_converter[] + + // tag::ArgumentsAccessor_example[] + @ParameterizedTest + @CsvSource( + "Jane, Doe, F, 1990-05-20", + "John, Doe, M, 1990-10-22" + ) + fun testWithArgumentsAccessor(arguments: ArgumentsAccessor) { + val person = + Person( + arguments.getString(0), + arguments.getString(1), + arguments.get(2, Gender::class.java), + arguments.get(3, LocalDate::class.java) + ) + + if (person.firstName == "Jane") { + assertEquals(Gender.F, person.gender) + } else { + assertEquals(Gender.M, person.gender) + } + assertEquals("Doe", person.lastName) + assertEquals(1990, person.dateOfBirth.year) + } + // end::ArgumentsAccessor_example[] + + // tag::ArgumentsAggregator_example[] + @ParameterizedTest + @CsvSource( + "Jane, Doe, F, 1990-05-20", + "John, Doe, M, 1990-10-22" + ) + fun testWithArgumentsAggregator( + @AggregateWith(PersonAggregator::class) person: Person + ) { + // perform assertions against person + } + + // end::ArgumentsAggregator_example[] + // tag::ArgumentsAggregator_example_PersonAggregator[] + class PersonAggregator : SimpleArgumentsAggregator() { + override fun aggregateArguments( + arguments: ArgumentsAccessor, + targetType: Class<*>, + context: AnnotatedElementContext, + parameterIndex: Int + ): Person = + Person( + arguments.getString(0), + arguments.getString(1), + arguments.get(2, Gender::class.java), + arguments.get(3, LocalDate::class.java) + ) + } + // end::ArgumentsAggregator_example_PersonAggregator[] + + // tag::ArgumentsAggregator_with_custom_annotation_example[] + @ParameterizedTest + @CsvSource( + "Jane, Doe, F, 1990-05-20", + "John, Doe, M, 1990-10-22" + ) + fun testWithCustomAggregatorAnnotation( + @CsvToPerson person: Person + ) { + // perform assertions against person + } + // end::ArgumentsAggregator_with_custom_annotation_example[] + + // tag::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[] + @Retention(AnnotationRetention.RUNTIME) + @Target(AnnotationTarget.VALUE_PARAMETER) + @AggregateWith(PersonAggregator::class) + annotation class CsvToPerson + // end::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[] + + // tag::custom_display_names[] + @DisplayName("Display name of container") + @ParameterizedTest(name = "{index} ==> the rank of {0} is {1}") + @CsvSource("apple, 1", "banana, 2", "'lemon, lime', 3") + fun testWithCustomDisplayNames( + fruit: String, + rank: Int + ) { + } + // end::custom_display_names[] + + // tag::named_arguments[] + @DisplayName("A parameterized test with named arguments") + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("namedArguments") + fun testWithNamedArguments(file: File) { + } + + fun namedArguments() = + sequenceOf( + arguments(named("An important file", File("path1"))), + arguments(named("Another file", File("path2"))) + ) + // end::named_arguments[] + + // tag::named_argument_set[] + @DisplayName("A parameterized test with named argument sets") + @ParameterizedTest + @FieldSource("argumentSets") + fun testWithArgumentSets( + file1: File, + file2: File + ) { + } + + val argumentSets = + listOf( + Arguments.argumentSet("Important files", File("path1"), File("path2")), + Arguments.argumentSet("Other files", File("path3"), File("path4")) + ) + // end::named_argument_set[] + + // tag::repeatable_annotations[] + @DisplayName("A parameterized test that makes use of repeatable annotations") + @ParameterizedTest + @MethodSource("someProvider") + @MethodSource("otherProvider") + fun testWithRepeatedAnnotation(argument: String) { + assertNotNull(argument) + } + + fun someProvider() = sequenceOf("foo") + + fun otherProvider() = sequenceOf("bar") + // end::repeatable_annotations[] + + @Disabled("Fails prior to invoking the test method") + // tag::argument_count_validation[] + @ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.STRICT) + @CsvSource("42, -666") + fun testWithArgumentCountValidation(number: Int) { + assertTrue(number > 0) + } + // end::argument_count_validation[] +} diff --git a/documentation/src/test/kotlin/example/kotlin/TaggingDemo.kt b/documentation/src/test/kotlin/example/kotlin/TaggingDemo.kt new file mode 100644 index 000000000000..1027dfe6a420 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/TaggingDemo.kt @@ -0,0 +1,25 @@ +/* + * 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 + +@Tag("fast") +@Tag("model") +class TaggingDemo { + @Test + @Tag("taxes") + fun testingTaxCalculation() { + } +} +// end::user_guide[]