From e6963608fc942569a64ee06677d6c91cad0386d3 Mon Sep 17 00:00:00 2001 From: Danil Pavlov Date: Thu, 30 Jul 2026 18:58:28 +0200 Subject: [PATCH 1/5] Add Kotlin tabs to Extensions topics Signed-off-by: Danil Pavlov --- .../pages/extensions/exception-handling.adoc | 58 +++++++++- .../extensions/intercepting-invocations.adoc | 18 +++- ...vocation-contexts-for-class-templates.adoc | 22 +++- ...nvocation-contexts-for-test-templates.adoc | 20 +++- ...ion-order-of-user-code-and-extensions.adoc | 100 ++++++++++++++++-- .../example/callbacks/DatabaseTestsDemo.java | 3 +- .../example/kotlin/ClassTemplateDemo.kt | 64 +++++++++++ .../kotlin/example/kotlin/TestTemplateDemo.kt | 61 +++++++++++ .../kotlin/callbacks/AbstractDatabaseTests.kt | 46 ++++++++ .../BrokenLifecycleMethodConfigDemo.kt | 54 ++++++++++ .../kotlin/callbacks/DatabaseTestsDemo.kt | 54 ++++++++++ .../example/kotlin/callbacks/Extension1.kt | 30 ++++++ .../example/kotlin/callbacks/Extension2.kt | 30 ++++++ .../kotlin/example/kotlin/callbacks/Logger.kt | 34 ++++++ .../exception/IgnoreIOExceptionExtension.kt | 29 +++++ .../exception/MultipleHandlersTestCase.kt | 55 ++++++++++ .../exception/RecordStateOnErrorExtension.kt | 55 ++++++++++ .../kotlin/interceptor/SwingEdtInterceptor.kt | 38 +++++++ .../kotlin/registration/DocumentationDemo.kt | 8 +- 19 files changed, 754 insertions(+), 25 deletions(-) create mode 100644 documentation/src/test/kotlin/example/kotlin/ClassTemplateDemo.kt create mode 100644 documentation/src/test/kotlin/example/kotlin/TestTemplateDemo.kt create mode 100644 documentation/src/test/kotlin/example/kotlin/callbacks/AbstractDatabaseTests.kt create mode 100644 documentation/src/test/kotlin/example/kotlin/callbacks/BrokenLifecycleMethodConfigDemo.kt create mode 100644 documentation/src/test/kotlin/example/kotlin/callbacks/DatabaseTestsDemo.kt create mode 100644 documentation/src/test/kotlin/example/kotlin/callbacks/Extension1.kt create mode 100644 documentation/src/test/kotlin/example/kotlin/callbacks/Extension2.kt create mode 100644 documentation/src/test/kotlin/example/kotlin/callbacks/Logger.kt create mode 100644 documentation/src/test/kotlin/example/kotlin/exception/IgnoreIOExceptionExtension.kt create mode 100644 documentation/src/test/kotlin/example/kotlin/exception/MultipleHandlersTestCase.kt create mode 100644 documentation/src/test/kotlin/example/kotlin/exception/RecordStateOnErrorExtension.kt create mode 100644 documentation/src/test/kotlin/example/kotlin/interceptor/SwingEdtInterceptor.kt diff --git a/documentation/modules/ROOT/pages/extensions/exception-handling.adoc b/documentation/modules/ROOT/pages/extensions/exception-handling.adoc index b4eb6d0eb213..58107321af17 100644 --- a/documentation/modules/ROOT/pages/extensions/exception-handling.adoc +++ b/documentation/modules/ROOT/pages/extensions/exception-handling.adoc @@ -10,27 +10,59 @@ and for those thrown during one of test lifecycle methods (`@BeforeAll`, `@Befor The following example shows an extension which will swallow all instances of `IOException` but rethrow any other type of exception. -[source,java,indent=0] .An exception handling extension that filters IOExceptions in test execution +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] ---- include::example$java/example/exception/IgnoreIOExceptionExtension.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/exception/IgnoreIOExceptionExtension.kt[tags=user_guide] +---- +-- +==== Another example shows how to record the state of an application under test exactly at -the point of unexpected exception being thrown during setup and cleanup. Note that unlike +the point of an unexpected exception being thrown during setup and cleanup. Note that unlike relying on lifecycle callbacks, which may or may not be executed depending on the test status, this solution guarantees execution immediately after failing `@BeforeAll`, `@BeforeEach`, `@AfterEach` or `@AfterAll`. -[source,java,indent=0] .An exception handling extension that records application state on error +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] ---- include::example$java/example/exception/RecordStateOnErrorExtension.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/exception/RecordStateOnErrorExtension.kt[tags=user_guide] +---- +-- +==== Multiple execution exception handlers may be invoked for the same lifecycle method in order of declaration. If one of the handlers swallows the handled exception, subsequent -ones will not be executed, and no failure will be propagated to JUnit engine, as if the +ones will not be executed, and no failure will be propagated to the JUnit engine, as if the exception was never thrown. Handlers may also choose to rethrow the exception or throw a different one, potentially wrapping the original. @@ -39,8 +71,24 @@ exceptions thrown during `@BeforeAll` or `@AfterAll` need to be registered on a while handlers for `BeforeEach` and `AfterEach` may be also registered for individual test methods. -[source,java,indent=0] .Registering multiple exception handling extensions +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] ---- include::example$java/example/exception/MultipleHandlersTestCase.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/exception/MultipleHandlersTestCase.kt[tags=user_guide] +---- +-- +==== diff --git a/documentation/modules/ROOT/pages/extensions/intercepting-invocations.adoc b/documentation/modules/ROOT/pages/extensions/intercepting-invocations.adoc index b3090668d4d3..dd7d183ecaf2 100644 --- a/documentation/modules/ROOT/pages/extensions/intercepting-invocations.adoc +++ b/documentation/modules/ROOT/pages/extensions/intercepting-invocations.adoc @@ -6,11 +6,27 @@ test code. The following example shows an extension that executes all test methods in Swing's Event Dispatch Thread. -[source,java,indent=0] .An extension that executes tests in a user-defined thread +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] ---- include::example$java/example/interceptor/SwingEdtInterceptor.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/interceptor/SwingEdtInterceptor.kt[tags=user_guide] +---- +-- +==== [NOTE] .Accessing the test-scoped `ExtensionContext` diff --git a/documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-class-templates.adoc b/documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-class-templates.adoc index fa1b7936b10a..e3cb54948bd8 100644 --- a/documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-class-templates.adoc +++ b/documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-class-templates.adoc @@ -9,11 +9,27 @@ will only be used for the next invocation of the `{ClassTemplate}`. The following example shows how to write a class template as well as how to register and implement a `{ClassTemplateInvocationContextProvider}`. +.A class template with an accompanying extension +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] -.A class template with accompanying extension ---- include::example$java/example/ClassTemplateDemo.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/ClassTemplateDemo.kt[tags=user_guide] +---- +-- +==== In this example, the class template will be invoked twice, meaning all test methods in the class template will be executed twice. The display names of the invocations will be @@ -33,9 +49,9 @@ output when using the `ConsoleLauncher` is as follows. The `{ClassTemplateInvocationContextProvider}` extension API is primarily intended for implementing different kinds of tests that rely on repetitive invocation of _all_ test -methods in a test class albeit in different contexts — for example, with different +methods in a test class, albeit in different contexts — for example, with different parameters, by preparing the test class instance differently, or multiple times without modifying the context. Please refer to the implementations of -xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Classes] which uses this extension +xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Classes] which use this extension point to provide its functionality. diff --git a/documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-test-templates.adoc b/documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-test-templates.adoc index 2d55a58a5ae3..5dec4b7ffafa 100644 --- a/documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-test-templates.adoc +++ b/documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-test-templates.adoc @@ -9,11 +9,27 @@ for the next invocation of the `{TestTemplate}` method. The following example shows how to write a test template as well as how to register and implement a `{TestTemplateInvocationContextProvider}`. +.A test template with an accompanying extension +[tabs] +==== +Java:: ++ +-- [source,java,indent=0] -.A test template with accompanying extension ---- include::example$java/example/TestTemplateDemo.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/TestTemplateDemo.kt[tags=user_guide] +---- +-- +==== In this example, the test template will be invoked twice. The display names of the invocations will be `apple` and `banana` as specified by the invocation context. Each @@ -28,7 +44,7 @@ parameter. The output when using the `ConsoleLauncher` is as follows. The `{TestTemplateInvocationContextProvider}` extension API is primarily intended for implementing different kinds of tests that rely on repetitive invocation of a test-like -method albeit in different contexts — for example, with different parameters, by preparing +method, albeit in different contexts — for example, with different parameters, by preparing the test class instance differently, or multiple times without modifying the context. Please refer to the implementations of xref:writing-tests/repeated-tests.adoc[] or xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Tests] which use this extension point diff --git a/documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc b/documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc index 6c88a1ed6d29..62f6ed3f8a24 100644 --- a/documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc +++ b/documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc @@ -10,7 +10,7 @@ NOTE: See also: xref:writing-tests/test-execution-order.adoc[] The following diagram illustrates the relative order of user-supplied code and extension code. User-supplied test and lifecycle methods are shown in orange, with callback code -implemented by extensions shown in blue. The grey box denotes the execution of a single +implemented by extensions shown in blue. The gray box denotes the execution of a single test method and will be repeated for every test method in the test class. [[diagram]] @@ -65,7 +65,7 @@ extension code executed after all tests of the container are executed In the simplest case only the actual test method will be executed (step 9); all other steps are optional depending on the presence of user code or extension support for the -corresponding lifecycle callback. For further details on the various lifecycle callbacks +corresponding lifecycle callback. For further details on the various lifecycle callbacks, please consult the respective Javadoc for each annotation and extension. All invocations of user code methods in the above table can additionally be intercepted @@ -122,29 +122,97 @@ interactions with the database. All methods imported statically from the `Logger log contextual information in order to help us better understand the execution order of user-supplied callback methods and callback methods in extensions. -[source,java,indent=0] .Extension1 +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] ---- include::example$java/example/callbacks/Extension1.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/callbacks/Extension1.kt[tags=user_guide] +---- +-- +==== -[source,java,indent=0] .Extension2 +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] ---- include::example$java/example/callbacks/Extension2.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/callbacks/Extension2.kt[tags=user_guide] +---- +-- +==== -[source,java,indent=0] .AbstractDatabaseTests +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] ---- include::example$java/example/callbacks/AbstractDatabaseTests.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/callbacks/AbstractDatabaseTests.kt[tags=user_guide] +---- + +NOTE: For conciseness, this snippet uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== -[source,java,indent=0] .DatabaseTestsDemo +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] ---- include::example$java/example/callbacks/DatabaseTestsDemo.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/callbacks/DatabaseTestsDemo.kt[tags=user_guide] +---- + +NOTE: For conciseness, this snippet assumes that the parent class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. +-- +==== When the `DatabaseTestsDemo` test class is executed, the following is logged. @@ -160,7 +228,7 @@ When the `DatabaseTestsDemo` test class is executed, the following is logged. @AfterEach AbstractDatabaseTests.disconnectFromDatabase() Extension2.afterEach() Extension1.afterEach() -@BeforeAll DatabaseTestsDemo.afterAll() +@AfterAll DatabaseTestsDemo.afterAll() @AfterAll AbstractDatabaseTests.destroyDatabase() ---- @@ -200,11 +268,27 @@ are executed. * The database connection is closed _before_ deleting the test data, which results in a failure to connect to the database. -[source,java,indent=0] .BrokenLifecycleMethodConfigDemo +[tabs] +==== +Java:: ++ +-- +[source,java,indent=0] ---- include::example$java/example/callbacks/BrokenLifecycleMethodConfigDemo.java[tags=user_guide] ---- +-- + +Kotlin:: ++ +-- +[source,kotlin,indent=0] +---- +include::example$kotlin/example/kotlin/callbacks/BrokenLifecycleMethodConfigDemo.kt[tags=user_guide] +---- +-- +==== When the `BrokenLifecycleMethodConfigDemo` test class is executed, the following is logged. diff --git a/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java b/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java index 6e518d23bab6..797e2a6d73a2 100644 --- a/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java +++ b/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java @@ -12,6 +12,7 @@ // tag::user_guide[] +import static example.callbacks.Logger.afterAllMethod; import static example.callbacks.Logger.afterEachMethod; import static example.callbacks.Logger.beforeAllMethod; import static example.callbacks.Logger.beforeEachMethod; @@ -54,7 +55,7 @@ void deleteTestDataFromDatabase() { @AfterAll static void afterAll() { - beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".afterAll()"); + afterAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".afterAll()"); } } diff --git a/documentation/src/test/kotlin/example/kotlin/ClassTemplateDemo.kt b/documentation/src/test/kotlin/example/kotlin/ClassTemplateDemo.kt new file mode 100644 index 000000000000..94c363ff1fdf --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/ClassTemplateDemo.kt @@ -0,0 +1,64 @@ +/* + * 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.ClassTemplate +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertNotNull +import org.junit.jupiter.api.extension.ClassTemplateInvocationContext +import org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extension +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.TestInstancePostProcessor +import java.util.stream.Stream + +// tag::user_guide[] +@ClassTemplate +@ExtendWith(ClassTemplateDemo.MyClassTemplateInvocationContextProvider::class) +class ClassTemplateDemo { + private var fruit: String? = null + + @Test + fun notNull() { + assertNotNull(fruit) + } + + @Test + fun wellKnown() { + assertTrue(fruit in WELL_KNOWN_FRUITS) + } + + class MyClassTemplateInvocationContextProvider : ClassTemplateInvocationContextProvider { + override fun supportsClassTemplate(context: ExtensionContext) = true + + override fun provideClassTemplateInvocationContexts(context: ExtensionContext): Stream = + Stream.of(invocationContext("apple"), invocationContext("banana")) + + private fun invocationContext(parameter: String) = + object : ClassTemplateInvocationContext { + override fun getDisplayName(invocationIndex: Int) = parameter + + override fun getAdditionalExtensions(): List = + listOf( + TestInstancePostProcessor { testInstance, _ -> + (testInstance as ClassTemplateDemo).fruit = parameter + } + ) + } + } + + companion object { + val WELL_KNOWN_FRUITS = listOf("apple", "banana", "lemon") + } +} +// end::user_guide[] diff --git a/documentation/src/test/kotlin/example/kotlin/TestTemplateDemo.kt b/documentation/src/test/kotlin/example/kotlin/TestTemplateDemo.kt new file mode 100644 index 000000000000..d2803218920c --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/TestTemplateDemo.kt @@ -0,0 +1,61 @@ +/* + * 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.TestTemplate +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extension +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.ParameterContext +import org.junit.jupiter.api.extension.ParameterResolver +import org.junit.jupiter.api.extension.TestTemplateInvocationContext +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider +import java.util.stream.Stream + +class TestTemplateDemo { + // tag::user_guide[] + val fruits = listOf("apple", "banana", "lemon") + + @TestTemplate + @ExtendWith(MyTestTemplateInvocationContextProvider::class) + fun testTemplate(fruit: String) { + assertTrue(fruit in fruits) + } + + class MyTestTemplateInvocationContextProvider : TestTemplateInvocationContextProvider { + override fun supportsTestTemplate(context: ExtensionContext) = true + + override fun provideTestTemplateInvocationContexts(context: ExtensionContext): Stream = + Stream.of(invocationContext("apple"), invocationContext("banana")) + + private fun invocationContext(parameter: String) = + object : TestTemplateInvocationContext { + override fun getDisplayName(invocationIndex: Int) = parameter + + override fun getAdditionalExtensions(): List = + listOf( + object : ParameterResolver { + override fun supportsParameter( + parameterContext: ParameterContext, + extensionContext: ExtensionContext + ) = parameterContext.parameter.type == String::class.java + + override fun resolveParameter( + parameterContext: ParameterContext, + extensionContext: ExtensionContext + ): Any = parameter + } + ) + } + } + // end::user_guide[] +} diff --git a/documentation/src/test/kotlin/example/kotlin/callbacks/AbstractDatabaseTests.kt b/documentation/src/test/kotlin/example/kotlin/callbacks/AbstractDatabaseTests.kt new file mode 100644 index 000000000000..0795db868032 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/callbacks/AbstractDatabaseTests.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.callbacks + +// tag::user_guide[] + +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.TestInstance + +/** + * Abstract base class for tests that use the database. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +abstract class AbstractDatabaseTests { + @BeforeAll + fun createDatabase() { + beforeAllMethod("${AbstractDatabaseTests::class.simpleName}.createDatabase()") + } + + @BeforeEach + fun connectToDatabase() { + beforeEachMethod("${AbstractDatabaseTests::class.simpleName}.connectToDatabase()") + } + + @AfterEach + fun disconnectFromDatabase() { + afterEachMethod("${AbstractDatabaseTests::class.simpleName}.disconnectFromDatabase()") + } + + @AfterAll + fun destroyDatabase() { + afterAllMethod("${AbstractDatabaseTests::class.simpleName}.destroyDatabase()") + } +} +// end::user_guide[] diff --git a/documentation/src/test/kotlin/example/kotlin/callbacks/BrokenLifecycleMethodConfigDemo.kt b/documentation/src/test/kotlin/example/kotlin/callbacks/BrokenLifecycleMethodConfigDemo.kt new file mode 100644 index 000000000000..5a4b35be11e4 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/callbacks/BrokenLifecycleMethodConfigDemo.kt @@ -0,0 +1,54 @@ +/* + * 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.callbacks + +// tag::user_guide[] + +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +/** + * Example of "broken" lifecycle method configuration. + * + * Test data is inserted before the database connection has been opened. + * + * Database connection is closed before deleting test data. + */ +@ExtendWith(Extension1::class, Extension2::class) +class BrokenLifecycleMethodConfigDemo { + @BeforeEach + fun connectToDatabase() { + beforeEachMethod("${javaClass.simpleName}.connectToDatabase()") + } + + @BeforeEach + fun insertTestDataIntoDatabase() { + beforeEachMethod("${javaClass.simpleName}.insertTestDataIntoDatabase()") + } + + @Test + fun testDatabaseFunctionality() { + testMethod("${javaClass.simpleName}.testDatabaseFunctionality()") + } + + @AfterEach + fun deleteTestDataFromDatabase() { + afterEachMethod("${javaClass.simpleName}.deleteTestDataFromDatabase()") + } + + @AfterEach + fun disconnectFromDatabase() { + afterEachMethod("${javaClass.simpleName}.disconnectFromDatabase()") + } +} +// end::user_guide[] diff --git a/documentation/src/test/kotlin/example/kotlin/callbacks/DatabaseTestsDemo.kt b/documentation/src/test/kotlin/example/kotlin/callbacks/DatabaseTestsDemo.kt new file mode 100644 index 000000000000..8f373204d642 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/callbacks/DatabaseTestsDemo.kt @@ -0,0 +1,54 @@ +/* + * 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.callbacks + +// tag::user_guide[] + +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +/** + * Extension of [AbstractDatabaseTests] that inserts test data + * into the database (after the database connection has been opened) + * and deletes test data (before the database connection is closed). + */ +@ExtendWith(Extension1::class, Extension2::class) +class DatabaseTestsDemo : AbstractDatabaseTests() { + @BeforeAll + fun beforeAll() { + beforeAllMethod("${javaClass.simpleName}.beforeAll()") + } + + @BeforeEach + fun insertTestDataIntoDatabase() { + beforeEachMethod("${javaClass.simpleName}.insertTestDataIntoDatabase()") + } + + @Test + fun testDatabaseFunctionality() { + testMethod("${javaClass.simpleName}.testDatabaseFunctionality()") + } + + @AfterEach + fun deleteTestDataFromDatabase() { + afterEachMethod("${javaClass.simpleName}.deleteTestDataFromDatabase()") + } + + @AfterAll + fun afterAll() { + afterAllMethod("${javaClass.simpleName}.afterAll()") + } +} +// end::user_guide[] diff --git a/documentation/src/test/kotlin/example/kotlin/callbacks/Extension1.kt b/documentation/src/test/kotlin/example/kotlin/callbacks/Extension1.kt new file mode 100644 index 000000000000..5fbb590bff47 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/callbacks/Extension1.kt @@ -0,0 +1,30 @@ +/* + * 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.callbacks + +// tag::user_guide[] + +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext + +class Extension1 : + BeforeEachCallback, + AfterEachCallback { + override fun beforeEach(context: ExtensionContext) { + beforeEachCallback(this) + } + + override fun afterEach(context: ExtensionContext) { + afterEachCallback(this) + } +} +// end::user_guide[] diff --git a/documentation/src/test/kotlin/example/kotlin/callbacks/Extension2.kt b/documentation/src/test/kotlin/example/kotlin/callbacks/Extension2.kt new file mode 100644 index 000000000000..1f8bccbc8d67 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/callbacks/Extension2.kt @@ -0,0 +1,30 @@ +/* + * 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.callbacks + +// tag::user_guide[] + +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext + +class Extension2 : + BeforeEachCallback, + AfterEachCallback { + override fun beforeEach(context: ExtensionContext) { + beforeEachCallback(this) + } + + override fun afterEach(context: ExtensionContext) { + afterEachCallback(this) + } +} +// end::user_guide[] diff --git a/documentation/src/test/kotlin/example/kotlin/callbacks/Logger.kt b/documentation/src/test/kotlin/example/kotlin/callbacks/Logger.kt new file mode 100644 index 000000000000..799ba8a195cd --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/callbacks/Logger.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.callbacks + +import org.junit.jupiter.api.extension.Extension +import java.util.logging.Logger + +private val logger = Logger.getLogger("example.kotlin.callbacks.Logger") + +fun beforeAllMethod(text: String) = log { "@BeforeAll $text" } + +fun beforeEachCallback(extension: Extension) = log { " ${extension.javaClass.simpleName}.beforeEach()" } + +fun beforeEachMethod(text: String) = log { " @BeforeEach $text" } + +fun testMethod(text: String) = log { " @Test $text" } + +fun afterEachMethod(text: String) = log { " @AfterEach $text" } + +fun afterEachCallback(extension: Extension) = log { " ${extension.javaClass.simpleName}.afterEach()" } + +fun afterAllMethod(text: String) = log { "@AfterAll $text" } + +private fun log(supplier: () -> String) { + logger.info(supplier) +} diff --git a/documentation/src/test/kotlin/example/kotlin/exception/IgnoreIOExceptionExtension.kt b/documentation/src/test/kotlin/example/kotlin/exception/IgnoreIOExceptionExtension.kt new file mode 100644 index 000000000000..e5680c1babb9 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/exception/IgnoreIOExceptionExtension.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.exception + +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler +import java.io.IOException + +// tag::user_guide[] +class IgnoreIOExceptionExtension : TestExecutionExceptionHandler { + override fun handleTestExecutionException( + context: ExtensionContext, + throwable: Throwable + ) { + when (throwable) { + is IOException -> return + else -> throw throwable + } + } +} +// end::user_guide[] diff --git a/documentation/src/test/kotlin/example/kotlin/exception/MultipleHandlersTestCase.kt b/documentation/src/test/kotlin/example/kotlin/exception/MultipleHandlersTestCase.kt new file mode 100644 index 000000000000..33ec4caa0246 --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/exception/MultipleHandlersTestCase.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.exception + +import example.kotlin.exception.MultipleHandlersTestCase.ThirdExecutedHandler +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler + +// tag::user_guide[] +// Register handlers for @Test, @BeforeEach, @AfterEach as well as @BeforeAll and @AfterAll +@ExtendWith(ThirdExecutedHandler::class) +class MultipleHandlersTestCase { + // Register handlers for @Test, @BeforeEach, @AfterEach only + @ExtendWith(SecondExecutedHandler::class) + @ExtendWith(FirstExecutedHandler::class) + @Test + fun testMethod() { + } + + // end::user_guide[] + + class FirstExecutedHandler : TestExecutionExceptionHandler { + override fun handleTestExecutionException( + context: ExtensionContext, + ex: Throwable + ): Unit = throw ex + } + + class SecondExecutedHandler : LifecycleMethodExecutionExceptionHandler { + override fun handleBeforeEachMethodExecutionException( + context: ExtensionContext, + ex: Throwable + ): Unit = throw ex + } + + class ThirdExecutedHandler : LifecycleMethodExecutionExceptionHandler { + override fun handleBeforeAllMethodExecutionException( + context: ExtensionContext, + ex: Throwable + ): Unit = throw ex + } + // tag::user_guide[] +} +// end::user_guide[] diff --git a/documentation/src/test/kotlin/example/kotlin/exception/RecordStateOnErrorExtension.kt b/documentation/src/test/kotlin/example/kotlin/exception/RecordStateOnErrorExtension.kt new file mode 100644 index 000000000000..4b24a7a7731d --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/exception/RecordStateOnErrorExtension.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.exception + +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler + +// tag::user_guide[] +class RecordStateOnErrorExtension : LifecycleMethodExecutionExceptionHandler { + override fun handleBeforeAllMethodExecutionException( + context: ExtensionContext, + ex: Throwable + ) { + memoryDumpForFurtherInvestigation("Failure recorded during class setup") + throw ex + } + + override fun handleBeforeEachMethodExecutionException( + context: ExtensionContext, + ex: Throwable + ) { + memoryDumpForFurtherInvestigation("Failure recorded during test setup") + throw ex + } + + override fun handleAfterEachMethodExecutionException( + context: ExtensionContext, + ex: Throwable + ) { + memoryDumpForFurtherInvestigation("Failure recorded during test cleanup") + throw ex + } + + override fun handleAfterAllMethodExecutionException( + context: ExtensionContext, + ex: Throwable + ) { + memoryDumpForFurtherInvestigation("Failure recorded during class cleanup") + throw ex + } + // end::user_guide[] + + private fun memoryDumpForFurtherInvestigation(error: String) { + } + // tag::user_guide[] +} +// end::user_guide[] diff --git a/documentation/src/test/kotlin/example/kotlin/interceptor/SwingEdtInterceptor.kt b/documentation/src/test/kotlin/example/kotlin/interceptor/SwingEdtInterceptor.kt new file mode 100644 index 000000000000..065aaacd3d5f --- /dev/null +++ b/documentation/src/test/kotlin/example/kotlin/interceptor/SwingEdtInterceptor.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.interceptor + +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.InvocationInterceptor +import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation +import org.junit.jupiter.api.extension.ReflectiveInvocationContext +import java.lang.reflect.Method +import javax.swing.SwingUtilities + +// tag::user_guide[] +class SwingEdtInterceptor : InvocationInterceptor { + override fun interceptTestMethod( + invocation: Invocation, + invocationContext: ReflectiveInvocationContext, + extensionContext: ExtensionContext + ) { + var throwable: Throwable? = null + SwingUtilities.invokeAndWait { + try { + invocation.proceed() + } catch (t: Throwable) { + throwable = t + } + } + throwable?.let { throw it } + } +} +// 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 index c955d7984106..5304d20c11bf 100644 --- a/documentation/src/test/kotlin/example/kotlin/registration/DocumentationDemo.kt +++ b/documentation/src/test/kotlin/example/kotlin/registration/DocumentationDemo.kt @@ -26,11 +26,9 @@ class DocumentationDemo { // use this.docs ... } - companion object { - fun lookUpDocsDir(): Path? { - // return path to docs dir - return null - } + private fun lookUpDocsDir(): Path? { + // return path to docs dir + return null } } // end::user_guide[] From 7813b4cd65948a6521420f8cf4a3ea1b200b9e83 Mon Sep 17 00:00:00 2001 From: Danil Pavlov Date: Wed, 19 Aug 2026 16:03:53 +0200 Subject: [PATCH 2/5] Fix companion objects Signed-off-by: Danil Pavlov --- ...ion-order-of-user-code-and-extensions.adoc | 4 ---- .../kotlin/callbacks/AbstractDatabaseTests.kt | 22 ++++++++++--------- .../kotlin/callbacks/DatabaseTestsDemo.kt | 20 ++++++++++------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc b/documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc index 62f6ed3f8a24..11e3ba229550 100644 --- a/documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc +++ b/documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc @@ -185,8 +185,6 @@ Kotlin:: ---- include::example$kotlin/example/kotlin/callbacks/AbstractDatabaseTests.kt[tags=user_guide] ---- - -NOTE: For conciseness, this snippet uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. -- ==== @@ -209,8 +207,6 @@ Kotlin:: ---- include::example$kotlin/example/kotlin/callbacks/DatabaseTestsDemo.kt[tags=user_guide] ---- - -NOTE: For conciseness, this snippet assumes that the parent class uses the `PER_CLASS` xref:writing-tests/test-instance-lifecycle.adoc[]. -- ==== diff --git a/documentation/src/test/kotlin/example/kotlin/callbacks/AbstractDatabaseTests.kt b/documentation/src/test/kotlin/example/kotlin/callbacks/AbstractDatabaseTests.kt index 0795db868032..7635490e0231 100644 --- a/documentation/src/test/kotlin/example/kotlin/callbacks/AbstractDatabaseTests.kt +++ b/documentation/src/test/kotlin/example/kotlin/callbacks/AbstractDatabaseTests.kt @@ -16,18 +16,11 @@ 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.TestInstance /** * Abstract base class for tests that use the database. */ -@TestInstance(TestInstance.Lifecycle.PER_CLASS) abstract class AbstractDatabaseTests { - @BeforeAll - fun createDatabase() { - beforeAllMethod("${AbstractDatabaseTests::class.simpleName}.createDatabase()") - } - @BeforeEach fun connectToDatabase() { beforeEachMethod("${AbstractDatabaseTests::class.simpleName}.connectToDatabase()") @@ -38,9 +31,18 @@ abstract class AbstractDatabaseTests { afterEachMethod("${AbstractDatabaseTests::class.simpleName}.disconnectFromDatabase()") } - @AfterAll - fun destroyDatabase() { - afterAllMethod("${AbstractDatabaseTests::class.simpleName}.destroyDatabase()") + companion object { + @JvmStatic + @BeforeAll + fun createDatabase() { + beforeAllMethod("${AbstractDatabaseTests::class.simpleName}.createDatabase()") + } + + @JvmStatic + @AfterAll + fun destroyDatabase() { + afterAllMethod("${AbstractDatabaseTests::class.simpleName}.destroyDatabase()") + } } } // end::user_guide[] diff --git a/documentation/src/test/kotlin/example/kotlin/callbacks/DatabaseTestsDemo.kt b/documentation/src/test/kotlin/example/kotlin/callbacks/DatabaseTestsDemo.kt index 8f373204d642..4e4592dc4b87 100644 --- a/documentation/src/test/kotlin/example/kotlin/callbacks/DatabaseTestsDemo.kt +++ b/documentation/src/test/kotlin/example/kotlin/callbacks/DatabaseTestsDemo.kt @@ -26,11 +26,6 @@ import org.junit.jupiter.api.extension.ExtendWith */ @ExtendWith(Extension1::class, Extension2::class) class DatabaseTestsDemo : AbstractDatabaseTests() { - @BeforeAll - fun beforeAll() { - beforeAllMethod("${javaClass.simpleName}.beforeAll()") - } - @BeforeEach fun insertTestDataIntoDatabase() { beforeEachMethod("${javaClass.simpleName}.insertTestDataIntoDatabase()") @@ -46,9 +41,18 @@ class DatabaseTestsDemo : AbstractDatabaseTests() { afterEachMethod("${javaClass.simpleName}.deleteTestDataFromDatabase()") } - @AfterAll - fun afterAll() { - afterAllMethod("${javaClass.simpleName}.afterAll()") + companion object { + @JvmStatic + @BeforeAll + fun beforeAll() { + beforeAllMethod("${DatabaseTestsDemo::class.simpleName}.beforeAll()") + } + + @JvmStatic + @AfterAll + fun afterAll() { + afterAllMethod("${DatabaseTestsDemo::class.simpleName}.afterAll()") + } } } // end::user_guide[] From 40b64e20bb4d133b48aeb237e55010c4ab7c50c0 Mon Sep 17 00:00:00 2001 From: Danil Pavlov Date: Tue, 1 Sep 2026 17:03:23 +0200 Subject: [PATCH 3/5] Add explanation about Kotlin imports Signed-off-by: Danil Pavlov --- .../relative-execution-order-of-user-code-and-extensions.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc b/documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc index 11e3ba229550..c4566e00bdc3 100644 --- a/documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc +++ b/documentation/modules/ROOT/pages/extensions/relative-execution-order-of-user-code-and-extensions.adoc @@ -119,8 +119,10 @@ for user-supplied _lifecycle methods_ (see xref:writing-tests/definitions.adoc[] The following examples demonstrate this behavior. Please note that the examples do not actually do anything realistic. Instead, they mimic common scenarios for testing interactions with the database. All methods imported statically from the `Logger` class -log contextual information in order to help us better understand the execution order of +log contextual information to help better understand the execution order of user-supplied callback methods and callback methods in extensions. +In Kotlin examples, the corresponding methods are top-level functions in the same package +and therefore don't require imports. .Extension1 [tabs] From b611a4b0200397e93e7db8947299351b376d3167 Mon Sep 17 00:00:00 2001 From: Danil Pavlov Date: Fri, 4 Sep 2026 12:32:09 +0200 Subject: [PATCH 4/5] Fix wording Signed-off-by: Danil Pavlov --- .../modules/ROOT/pages/extensions/exception-handling.adoc | 2 +- .../providing-invocation-contexts-for-class-templates.adoc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/modules/ROOT/pages/extensions/exception-handling.adoc b/documentation/modules/ROOT/pages/extensions/exception-handling.adoc index 58107321af17..563c7efbbd72 100644 --- a/documentation/modules/ROOT/pages/extensions/exception-handling.adoc +++ b/documentation/modules/ROOT/pages/extensions/exception-handling.adoc @@ -33,7 +33,7 @@ include::example$kotlin/example/kotlin/exception/IgnoreIOExceptionExtension.kt[t ==== Another example shows how to record the state of an application under test exactly at -the point of an unexpected exception being thrown during setup and cleanup. Note that unlike +the point where an unexpected exception is thrown during setup and cleanup. Note that unlike relying on lifecycle callbacks, which may or may not be executed depending on the test status, this solution guarantees execution immediately after failing `@BeforeAll`, `@BeforeEach`, `@AfterEach` or `@AfterAll`. diff --git a/documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-class-templates.adoc b/documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-class-templates.adoc index e3cb54948bd8..0ceafaff6093 100644 --- a/documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-class-templates.adoc +++ b/documentation/modules/ROOT/pages/extensions/providing-invocation-contexts-for-class-templates.adoc @@ -52,6 +52,6 @@ implementing different kinds of tests that rely on repetitive invocation of _all methods in a test class, albeit in different contexts — for example, with different parameters, by preparing the test class instance differently, or multiple times without modifying the context. -Please refer to the implementations of -xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Classes] which use this extension +Please refer to the implementation of +xref:writing-tests/parameterized-classes-and-tests.adoc[Parameterized Classes] which uses this extension point to provide its functionality. From 645ac8215931934fe1801e1c261340594c2f39d8 Mon Sep 17 00:00:00 2001 From: Danil Pavlov Date: Tue, 22 Sep 2026 14:51:07 +0200 Subject: [PATCH 5/5] Fix return type Signed-off-by: Danil Pavlov --- .../src/test/kotlin/example/kotlin/ClassTemplateDemo.kt | 2 +- .../src/test/kotlin/example/kotlin/TestTemplateDemo.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/src/test/kotlin/example/kotlin/ClassTemplateDemo.kt b/documentation/src/test/kotlin/example/kotlin/ClassTemplateDemo.kt index 94c363ff1fdf..80c693713606 100644 --- a/documentation/src/test/kotlin/example/kotlin/ClassTemplateDemo.kt +++ b/documentation/src/test/kotlin/example/kotlin/ClassTemplateDemo.kt @@ -41,7 +41,7 @@ class ClassTemplateDemo { class MyClassTemplateInvocationContextProvider : ClassTemplateInvocationContextProvider { override fun supportsClassTemplate(context: ExtensionContext) = true - override fun provideClassTemplateInvocationContexts(context: ExtensionContext): Stream = + override fun provideClassTemplateInvocationContexts(context: ExtensionContext) = Stream.of(invocationContext("apple"), invocationContext("banana")) private fun invocationContext(parameter: String) = diff --git a/documentation/src/test/kotlin/example/kotlin/TestTemplateDemo.kt b/documentation/src/test/kotlin/example/kotlin/TestTemplateDemo.kt index d2803218920c..40d2eb20d0dd 100644 --- a/documentation/src/test/kotlin/example/kotlin/TestTemplateDemo.kt +++ b/documentation/src/test/kotlin/example/kotlin/TestTemplateDemo.kt @@ -34,7 +34,7 @@ class TestTemplateDemo { class MyTestTemplateInvocationContextProvider : TestTemplateInvocationContextProvider { override fun supportsTestTemplate(context: ExtensionContext) = true - override fun provideTestTemplateInvocationContexts(context: ExtensionContext): Stream = + override fun provideTestTemplateInvocationContexts(context: ExtensionContext) = Stream.of(invocationContext("apple"), invocationContext("banana")) private fun invocationContext(parameter: String) =