JUnit Platform TestEngine implementation for KotlinTest framework, enabling KotlinTest specifications to run as JUnit 5 tests
npx @tessl/cli install tessl/maven-io-kotlintest--kotlintest-runner-junit5@3.4.0KotlinTest Runner JUnit5 is a JUnit Platform TestEngine implementation that integrates the KotlinTest testing framework with JUnit 5 ecosystem. It enables KotlinTest specifications to be discovered and executed by JUnit Platform launchers, allowing KotlinTest's expressive testing styles to work seamlessly with JUnit 5 tooling including IDEs, build tools like Gradle and Maven, and CI/CD systems.
testImplementation 'io.kotlintest:kotlintest-runner-junit5:3.4.2'import io.kotlintest.runner.junit5.KotlinTestEngine
import io.kotlintest.specs.*For test specifications, extend from the spec classes:
import io.kotlintest.specs.StringSpec
import io.kotlintest.specs.FunSpec
import io.kotlintest.specs.BehaviorSpec
// ... other spec types// Example test using StringSpec with JUnit 5 integration
import io.kotlintest.specs.StringSpec
import io.kotlintest.shouldBe
class CalculatorTest : StringSpec({
"addition should work correctly" {
val result = 2 + 2
result shouldBe 4
}
"multiplication should work correctly" {
val result = 3 * 4
result shouldBe 12
}
})The engine is automatically discovered by JUnit Platform through service loading, so no explicit configuration is needed beyond adding the dependency.
The integration works through several key components:
KotlinTestEngine implements JUnit Platform's TestEngine interfaceCore JUnit Platform TestEngine that discovers and executes KotlinTest specifications.
class KotlinTestEngine : TestEngine {
companion object {
const val EngineId = "kotlintest"
}
override fun getId(): String
override fun discover(
request: EngineDiscoveryRequest,
uniqueId: UniqueId
): KotlinTestEngineDescriptor
override fun execute(request: ExecutionRequest)
class KotlinTestEngineDescriptor(
id: UniqueId,
val classes: List<KClass<out Spec>>
) : EngineDescriptor {
override fun mayRegisterTests(): Boolean
}
}JUnit Platform execution listener that bridges KotlinTest's execution events to JUnit Platform's reporting model.
class JUnitTestRunnerListener(
private val listener: EngineExecutionListener,
val root: EngineDescriptor
) : TestEngineListener {
data class ResultState(val testCase: TestCase, val result: TestResult)
override fun engineStarted(classes: List<KClass<out Spec>>)
override fun engineFinished(t: Throwable?)
override fun beforeSpecClass(klass: KClass<out Spec>)
override fun afterSpecClass(klass: KClass<out Spec>, t: Throwable?)
override fun enterTestCase(testCase: TestCase)
override fun invokingTestCase(testCase: TestCase, k: Int)
override fun exitTestCase(testCase: TestCase, result: TestResult)
override fun specInitialisationFailed(klass: KClass<out Spec>, t: Throwable)
}Synchronized wrapper around EngineExecutionListener to handle concurrent test execution safely.
class SynchronizedEngineExecutionListener(
val listener: EngineExecutionListener
) : EngineExecutionListener {
override fun executionFinished(
testDescriptor: TestDescriptor?,
testExecutionResult: TestExecutionResult?
)
override fun reportingEntryPublished(
testDescriptor: TestDescriptor?,
entry: ReportEntry?
)
override fun executionSkipped(
testDescriptor: TestDescriptor?,
reason: String?
)
override fun executionStarted(testDescriptor: TestDescriptor?)
override fun dynamicTestRegistered(testDescriptor: TestDescriptor?)
}Utility function that converts JUnit Platform discovery requests to KotlinTest's internal discovery format.
/**
* Returns a KotlinTest [DiscoveryRequest] built from the selectors and filters present
* in the JUnit [EngineDiscoveryRequest].
*
* Supported selectors are:
* - [ClassSelector] - used to specify a single class by fully qualified name
* - [DirectorySelector] - classes are scanned in the given directory
* - [UriSelector] - classes are scanned from the given uri
* - [PackageSelector] - classes are scanned on the default classpath for the given package name
*
* Support filters are:
* - [ClassNameFilter] - filters out specs based on a classname
* - [PackageNameFilter] - filters out specs based on package names
*
* Unsupported selectors are:
* - [MethodSelector] - not supported because kotlintest does not define tests as methods/functions
*/
internal fun discoveryRequest(request: EngineDiscoveryRequest): DiscoveryRequestAbstract spec classes that provide better IntelliJ IDEA integration through marker interface and annotations.
interface IntelliMarker {
@EnabledIfSystemProperty(named = "wibble", matches = "wobble")
@TestFactory
fun primer() {
}
}
abstract class AnnotationSpec(
body: AbstractAnnotationSpec.() -> Unit = {}
) : AbstractAnnotationSpec(body), IntelliMarker
abstract class BehaviorSpec(
body: AbstractBehaviorSpec.() -> Unit = {}
) : AbstractBehaviorSpec(body), IntelliMarker
abstract class DescribeSpec(
body: AbstractDescribeSpec.() -> Unit = {}
) : AbstractDescribeSpec(body), IntelliMarker
abstract class ExpectSpec(
body: AbstractExpectSpec.() -> Unit = {}
) : AbstractExpectSpec(body), IntelliMarker
abstract class FeatureSpec(
body: AbstractFeatureSpec.() -> Unit = {}
) : AbstractFeatureSpec(body), IntelliMarker
abstract class FreeSpec(
body: AbstractFreeSpec.() -> Unit = {}
) : AbstractFreeSpec(body), IntelliMarker
abstract class FunSpec(
body: AbstractFunSpec.() -> Unit = {}
) : AbstractFunSpec(body), IntelliMarker
abstract class ShouldSpec(
body: AbstractShouldSpec.() -> Unit = {}
) : AbstractShouldSpec(body), IntelliMarker {
infix fun String.should(matcher: Matcher<String>): Unit
}
abstract class StringSpec(
body: AbstractStringSpec.() -> Unit = {}
) : AbstractStringSpec(body), IntelliMarker
abstract class WordSpec(
body: AbstractWordSpec.() -> Unit = {}
) : AbstractWordSpec(body), IntelliMarker {
infix fun String?.should(matcher: Matcher<String?>): Unit
}Extension functions for debugging JUnit Platform discovery requests.
fun EngineDiscoveryRequest.string(): String
fun LauncherDiscoveryRequest.string(): StringExtension function for creating spec-specific unique IDs in the JUnit Platform descriptor hierarchy.
fun UniqueId.appendSpec(description: Description): UniqueId// From KotlinTest framework (imported dependencies)
interface Spec
interface TestEngineListener
interface TestCase
interface TestResult
interface TestStatus
interface Description
class Matcher<T>
// From JUnit Platform (imported dependencies)
interface TestEngine
interface EngineDiscoveryRequest
interface ExecutionRequest
interface TestDescriptor
interface EngineExecutionListener
interface TestExecutionResult
interface ReportEntry
class UniqueId
class EngineDescriptorThe engine is automatically registered with JUnit Platform through Java's ServiceLoader mechanism:
META-INF/services/org.junit.platform.engine.TestEngineio.kotlintest.runner.junit5.KotlinTestEngineThis allows JUnit Platform to automatically discover and use the KotlinTest engine without explicit configuration.
The engine handles various error scenarios: