extend kotest DescribeSpec

Note with example on how to extend a DescribeSpec in kotest.

GT-Sandbox-Snapshot: Extend DescribeSpec to add interception (before & after test)

In the following example we extend and intercept (related kotest DescribeSpec interception)

Code

package com.glassthought.sandbox

import io.kotest.core.extensions.TestCaseExtension
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.core.test.TestCase
import io.kotest.core.test.TestResult
import gt.sandbox.util.output.Out
import com.glassthought.sandbox.sandbox.util.out.impl.OutSettings

val out = Out.standard(OutSettings(printCoroutineName = true))

// Custom extension for test case interception
class LoggingTestCaseExtension : TestCaseExtension {
  override suspend fun intercept(
    testCase: TestCase,
    execute: suspend (TestCase) -> TestResult
  ): TestResult {
    out.printlnBlue("Before executing test: ${testCase.name.testName}")
    val result = execute(testCase)
    out.printlnGreen("After executing test: ${testCase.name.testName}")
    return result
  }
}

// Custom DescribeSpec class
abstract class CustomDescribeSpec(body: CustomDescribeSpec.() -> Unit = {}) : DescribeSpec() {

  init {
    extension(LoggingTestCaseExtension()) // Add logging extension
    body() // Execute the user-defined spec body
  }
}

class MyCustomDescribeSpec : CustomDescribeSpec({
  describe("outer-describe") {
    describe("nested-describe-1") {
      it("it-in-nested-describe") {
        out.println("Running test inside inner describe")
      }
    }
    it("test-1 in outer describe") {
      out.println("Running test-1 in outer describe")
    }
    it("test-2 in outer describe") {
      out.println("Running test-2 in outer describe")
    }
  }
})

Command to reproduce:

gt.sandbox.checkout.commit f26ddf425b14c7868de8 \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "./gradlew test --rerun-tasks"

Recorded output of command:

> Task :app:checkKotlinGradlePluginConfigurationErrors SKIPPED
> Task :app:processResources NO-SOURCE
> Task :app:processTestResources NO-SOURCE
> Task :app:compileKotlin
> Task :app:compileJava NO-SOURCE
> Task :app:classes UP-TO-DATE
> Task :app:compileTestKotlin
> Task :app:compileTestJava NO-SOURCE
> Task :app:testClasses UP-TO-DATE

> Task :app:test

Gradle Test Executor 2 STANDARD_OUT
    Warning: Kotest autoscan is enabled. This means Kotest will scan the classpath for extensions that are annotated with @AutoScan. To avoid this startup cost, disable autoscan by setting the system property 'kotest.framework.classpath.scanning.autoscan.disable=true'. In 6.0 this value will default to true. For further details see https://kotest.io/docs/next/framework/project-config.html#runtime-detection

com.glassthought.sandbox.MyCustomDescribeSpec STANDARD_OUT
    Before executing test: outer-describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe STANDARD_OUT
    Before executing test: nested-describe-1

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > nested-describe-1 STANDARD_OUT
    Before executing test: it-in-nested-describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > nested-describe-1 > com.glassthought.sandbox.MyCustomDescribeSpec.it-in-nested-describe STARTED

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > nested-describe-1 > com.glassthought.sandbox.MyCustomDescribeSpec.it-in-nested-describe STANDARD_OUT
    Running test inside inner describe
    After executing test: it-in-nested-describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > nested-describe-1 > com.glassthought.sandbox.MyCustomDescribeSpec.it-in-nested-describe PASSED

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > nested-describe-1 STANDARD_OUT
    After executing test: nested-describe-1

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe STANDARD_OUT
    Before executing test: test-1 in outer describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > test-1 in outer describe STARTED

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > test-1 in outer describe STANDARD_OUT
    Running test-1 in outer describe
    After executing test: test-1 in outer describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > test-1 in outer describe PASSED

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe STANDARD_OUT
    Before executing test: test-2 in outer describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > test-2 in outer describe STARTED

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > test-2 in outer describe STANDARD_OUT
    Running test-2 in outer describe
    After executing test: test-2 in outer describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > test-2 in outer describe PASSED

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe STANDARD_OUT
    After executing test: outer-describe

BUILD SUCCESSFUL in 3s
3 actionable tasks: 3 executed
GT-Sandbox-Snapshot: Extend with before and after entire spec

Code

package com.glassthought.sandbox

import io.kotest.core.extensions.TestCaseExtension
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.core.test.TestCase
import io.kotest.core.test.TestResult
import gt.sandbox.util.output.Out
import com.glassthought.sandbox.sandbox.util.out.impl.OutSettings
import io.kotest.core.spec.Spec

val out = Out.standard(OutSettings(printCoroutineName = true))

// Custom extension for test case interception
class LoggingTestCaseExtension : TestCaseExtension {
  override suspend fun intercept(
    testCase: TestCase,
    execute: suspend (TestCase) -> TestResult
  ): TestResult {
    out.printlnBlue("Before executing test: ${testCase.name.testName}")
    val result = execute(testCase)
    out.printlnGreen("After executing test: ${testCase.name.testName}")
    return result
  }
}

// Custom DescribeSpec class
abstract class CustomDescribeSpec(body: CustomDescribeSpec.() -> Unit = {}) : DescribeSpec() {

  init {
    extension(LoggingTestCaseExtension()) // Add logging extension
    body() // Execute the user-defined spec body
  }

  override suspend fun beforeSpec(spec: Spec) {
    super.beforeSpec(spec)
    out.printlnOrange("Before the entire spec: ${spec::class.simpleName}")
  }

  override suspend fun afterSpec(spec: Spec) {
    super.afterSpec(spec)
    out.printlnOrange("After the entire spec: ${spec::class.simpleName}")
  }
}

class MyCustomDescribeSpec : CustomDescribeSpec({
  describe("outer-describe") {
    describe("nested-describe-1") {
      it("it-in-nested-describe") {
        out.println("Running test inside inner describe")
      }
    }
    it("test-1 in outer describe") {
      out.println("Running test-1 in outer describe")
    }
    it("test-2 in outer describe") {
      out.println("Running test-2 in outer describe")
    }
  }
})

Command to reproduce:

gt.sandbox.checkout.commit 6784418679fee923d907 \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "./gradlew test --rerun-tasks"

Recorded output of command:

> Task :app:checkKotlinGradlePluginConfigurationErrors SKIPPED
> Task :app:processResources NO-SOURCE
> Task :app:processTestResources NO-SOURCE
> Task :app:compileKotlin
> Task :app:compileJava NO-SOURCE
> Task :app:classes UP-TO-DATE
> Task :app:compileTestKotlin
> Task :app:compileTestJava NO-SOURCE
> Task :app:testClasses UP-TO-DATE

> Task :app:test

Gradle Test Executor 2 STANDARD_OUT
    Warning: Kotest autoscan is enabled. This means Kotest will scan the classpath for extensions that are annotated with @AutoScan. To avoid this startup cost, disable autoscan by setting the system property 'kotest.framework.classpath.scanning.autoscan.disable=true'. In 6.0 this value will default to true. For further details see https://kotest.io/docs/next/framework/project-config.html#runtime-detection

com.glassthought.sandbox.MyCustomDescribeSpec STANDARD_OUT
    Before the entire spec: MyCustomDescribeSpec
    Before executing test: outer-describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe STANDARD_OUT
    Before executing test: nested-describe-1

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > nested-describe-1 STANDARD_OUT
    Before executing test: it-in-nested-describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > nested-describe-1 > com.glassthought.sandbox.MyCustomDescribeSpec.it-in-nested-describe STARTED

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > nested-describe-1 > com.glassthought.sandbox.MyCustomDescribeSpec.it-in-nested-describe STANDARD_OUT
    Running test inside inner describe
    After executing test: it-in-nested-describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > nested-describe-1 > com.glassthought.sandbox.MyCustomDescribeSpec.it-in-nested-describe PASSED

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > nested-describe-1 STANDARD_OUT
    After executing test: nested-describe-1

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe STANDARD_OUT
    Before executing test: test-1 in outer describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > test-1 in outer describe STARTED

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > test-1 in outer describe STANDARD_OUT
    Running test-1 in outer describe
    After executing test: test-1 in outer describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > test-1 in outer describe PASSED

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe STANDARD_OUT
    Before executing test: test-2 in outer describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > test-2 in outer describe STARTED

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > test-2 in outer describe STANDARD_OUT
    Running test-2 in outer describe
    After executing test: test-2 in outer describe

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe > test-2 in outer describe PASSED

com.glassthought.sandbox.MyCustomDescribeSpec > outer-describe STANDARD_OUT
    After executing test: outer-describe

com.glassthought.sandbox.MyCustomDescribeSpec STANDARD_OUT
    After the entire spec: MyCustomDescribeSpec

BUILD SUCCESSFUL in 2s
3 actionable tasks: 3 executed