diff --git a/kotest-framework/kotest-framework-api/src/commonMain/kotlin/io/kotest/core/extensions/MountableExtension.kt b/kotest-framework/kotest-framework-api/src/commonMain/kotlin/io/kotest/core/extensions/MountableExtension.kt index f7f9466b389..df37a91ebc4 100644 --- a/kotest-framework/kotest-framework-api/src/commonMain/kotlin/io/kotest/core/extensions/MountableExtension.kt +++ b/kotest-framework/kotest-framework-api/src/commonMain/kotlin/io/kotest/core/extensions/MountableExtension.kt @@ -27,11 +27,49 @@ interface MountableExtension : Extension { fun mount(configure: CONFIG.() -> Unit): MATERIALIZED } +/** + * A [LazyMountableExtension] is an [Extension] that can return a materialized value to the + * user and allows for a configuration block. + * + * This allows extensions to return control objects which differ from the extension itself. + * + * For example: + * + * class MyTest : FunSpec() { + * init { + * val kafka = install(EmbeddedKafka) { + * port = 9092 + * } + * } + * } + * + * Here `kafka` is a materialized value that contains details of the host/port of the + * started kafka instance and `EmbeddedKafka` is the extension itself. + * + */ +interface LazyMountableExtension : Extension { + // cannot be suspending as it is invoked by install that is used in constructors + fun mount(configure: CONFIG.() -> Unit): LazyMaterialized +} + +interface LazyMaterialized { + suspend fun get(): MATERIALIZED +} + // cannot be suspending as it is used in constructors fun Spec.install( - mountable: MountableExtension, + ext: MountableExtension, configure: CONFIG.() -> Unit = {} ): MATERIALIZED { - extensions(mountable) - return mountable.mount(configure) + extensions(ext) + return ext.mount(configure) +} + +// cannot be suspending as it is used in constructors +fun Spec.install( + ext: LazyMountableExtension, + configure: CONFIG.() -> Unit = {}, +): LazyMaterialized { + extensions(ext) + return ext.mount(configure) } diff --git a/kotest-framework/kotest-framework-engine/src/jvmTest/kotlin/com/sksamuel/kotest/engine/extensions/LazyMountableExtensionTest.kt b/kotest-framework/kotest-framework-engine/src/jvmTest/kotlin/com/sksamuel/kotest/engine/extensions/LazyMountableExtensionTest.kt new file mode 100644 index 00000000000..78544891e69 --- /dev/null +++ b/kotest-framework/kotest-framework-engine/src/jvmTest/kotlin/com/sksamuel/kotest/engine/extensions/LazyMountableExtensionTest.kt @@ -0,0 +1,36 @@ +package com.sksamuel.kotest.engine.extensions + +import io.kotest.core.extensions.LazyMaterialized +import io.kotest.core.extensions.LazyMountableExtension +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.delay + +class LazyMountableExtensionTest : FunSpec() { + + private val mountable = MyLazyMountableExtension() + private val m: LazyMaterialized = install(mountable) + + init { + test("lazy materialized values") { + m.get() shouldBe "ready" + } + } +} + +class MyLazyMountableExtension : LazyMountableExtension { + + override fun mount(configure: (Unit) -> Unit): LazyMaterialized { + return object : LazyMaterialized { + + var state: String? = null + + override suspend fun get(): String { + delay(1) + if (state == null) state = "ready" + return state ?: error("Must be initialized") + } + } + } +}