CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/jvm-unit-tests

JVM unit testing (Java / Kotlin / Scala / Groovy) with JUnit 5 (Jupiter) as the primary framework - annotations (`@Test` / `@ParameterizedTest` / source providers), lifecycle hooks (`@BeforeAll` / `@BeforeEach`), extension model (`@ExtendWith` + Mockito/Spring), display names, conditional execution, parallel-execution config, JaCoCo coverage, and Maven Surefire / Gradle CI. Includes a per-language framework decision table (Java → JUnit 5, Kotlin → Kotest, Groovy → Spock, Scala → ScalaTest, legacy → TestNG; always match an existing build convention) and test-authoring conventions (framework detection from pom.xml / build.gradle / build.sbt, path conventions, no fabricated methods). References cover Kotest spec styles, Spock given/when/then + data tables, TestNG DataProviders + suites, ScalaTest styles + Matchers, and the AssertJ fluent-assertion catalog. Use for any JVM unit-test task: choosing or configuring a framework, writing or parameterizing tests, wiring coverage and CI.

72

Quality

91%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

kotest.mdreferences/

Kotest - Kotlin-native testing (reference)

Companion reference for jvm-unit-tests. Consult for Kotlin-only or Kotlin-primary projects that want a Kotlin-idiomatic DSL over JUnit 5's annotation-driven approach. For multi-language JVM projects, JUnit 5 is more universal (SKILL.md).

Per kotest.io/docs:

Kotest differs from JUnit 5 by: multiple specification styles (DSL choice per team), rich matchers (shouldBe, shouldContain, shouldThrow), built-in property-based testing, coroutines-first test bodies, and spec-level isolation modes. Matchers are bundled with the runner and are not a drop-in standalone assertion library - for assertion-only use with JUnit 5 / TestNG / Spock see assertj.md.

Install

build.gradle.kts:

dependencies {
    testImplementation("io.kotest:kotest-runner-junit5:5.9.1")
    testImplementation("io.kotest:kotest-assertions-core:5.9.1")
    testImplementation("io.kotest:kotest-property:5.9.1")   // for property-based
}

tasks.test {
    useJUnitPlatform()
}

Specification styles

Per kt-docs, 8+ styles. Common picks - one style per project:

StringSpec (terse, no nesting):

class CalculatorTest : StringSpec({
    "adds two numbers" {
        Calculator.add(1, 2) shouldBe 3
    }
    "throws on overflow" {
        shouldThrow<ArithmeticException> {
            Calculator.add(Int.MAX_VALUE, 1)
        }
    }
})

FunSpec (most familiar to JUnit / pytest users):

class CalculatorTest : FunSpec({
    test("adds two numbers") {
        Calculator.add(1, 2) shouldBe 3
    }
    context("overflow handling") {
        test("throws on max + 1") {
            shouldThrow<ArithmeticException> { Calculator.add(Int.MAX_VALUE, 1) }
        }
    }
})

BehaviorSpec (Given/When/Then BDD): given("…") { \when`("…") { then("…") { … } } }`.

Matchers

Per kotest.io/docs/assertions/matchers.html:

MatcherUse
value shouldBe expected / shouldNotBeEquality
value.shouldBeNull() / shouldNotBeNull()Null check
string.shouldContain("substring") / shouldStartWith / shouldEndWithString
string.shouldMatch(regex)Regex
list.shouldHaveSize(3) / shouldContainExactly(...) / shouldContainAll(...)Collection
map.shouldContainKey("key") / shouldContainValue("v")Map
value.shouldBeInstanceOf<MyClass>()Type
result.shouldBeSuccess() / shouldBeFailure()Kotlin Result
shouldThrow<E> { ... }Exception

Property-based testing (built-in)

"addition is commutative" {
    checkAll<Int, Int> { a, b ->
        a + b shouldBe b + a
    }
}
"concatenation length" {
    checkAll(Arb.string(), Arb.string()) { a, b ->
        (a + b).length shouldBe a.length + b.length
    }
}

For deeper property-based work see the qa-property-based plugin (jqwik-testing for the JVM). Don't run Kotest property-based and jqwik in the same project - pick one.

Coroutines and data-driven tests

Test bodies are suspend functions - runTest etc. from kotlinx-coroutines-test work directly:

"fetches user data" {
    val user = fetchUserAsync(1)   // suspend function
    user.id shouldBe 1
}

Data-driven rows via withData - each row reports as a separate test:

context("addition") {
    withData(
        Triple(1, 2, 3),
        Triple(0, 0, 0),
        Triple(-1, 1, 0),
    ) { (a, b, expected) ->
        (a + b) shouldBe expected
    }
}

Isolation modes

Per kotest.io/docs/framework/isolation-mode.html:

ModeBehavior
SingleInstanceOne spec instance for all tests (default; fastest)
InstancePerTestFresh spec instance per test (incl. nested contexts)
InstancePerLeafFresh spec instance per leaf-test only

Set per-spec (isolationMode = IsolationMode.InstancePerTest inside the spec body) or globally via AbstractProjectConfig. Use a fresh-instance mode whenever specs carry mutable state.

CI

Kotest's runner is kotest-runner-junit5, so CI is identical to JUnit 5: ./gradlew test jacocoTestReport.

Anti-patterns

Anti-patternWhy it failsFix
Mix multiple spec styles in one projectReader confusionPick one
Default isolation + shared mutable stateTests interfereInstancePerTest
assertEquals(a, b) (JUnit style) in Kotest specsMixes paradigmsa shouldBe b

References

SKILL.md

tile.json