kotest-tests
Configures and runs Kotest - Kotlin-native test framework with multiple specification styles (StringSpec, FunSpec, BehaviorSpec, DescribeSpec, ShouldSpec, FreeSpec, FeatureSpec, ExpectSpec, AnnotationSpec); rich matcher library; built-in property-based testing (alternative to jqwik); coroutines support; data-driven testing; isolation modes per-spec or per-test; integrates with Gradle JVM test task. Use when working with Kotlin and wanting Kotlin-idiomatic DSL over JUnit 5's annotation-driven approach. Matchers (shouldBe, shouldContain) are bundled with the runner and are not a drop-in replacement for a standalone JVM assertion library; for assertion-only use paired with JUnit 5 / TestNG / Spock see assertj.
Install with skills.sh (any agent)
npx skills add testland/qa --skill kotest-testskotest-tests
Overview
Per kotest.io/docs (opens in new window):
Kotest is the Kotlin-native test framework. Differentiated from JUnit 5 (which works fine with Kotlin too) by:
For multi-language JVM projects, JUnit 5 is more universal. For Kotlin-only or Kotlin-primary, Kotest's DSL is more ergonomic.
How to use
Step 1 - 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()
}Step 2 - Specification styles
Per kt-docs (opens in new window) Kotest supports 8+ styles. Common picks:
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):
class UserServiceTest : BehaviorSpec({
given("a registered user") {
val user = User("alice@example.com")
`when`("they update their email") {
user.updateEmail("new@example.com")
then("the email is updated") {
user.email shouldBe "new@example.com"
}
}
}
})Pick one style per project + stick with it.
Step 3 - Matchers
Assert with the rich matcher library - equality, null, string, collection, map, type, Kotlin Result, and shouldThrow<E>. Full catalog in references/matchers-and-isolation.md.
Step 4 - Property-based testing
Built-in (no separate library):
class PropertyTest : StringSpec({
"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 jqwik-testing or the dedicated qa-property-based plugin.
Step 5 - Coroutines
class AsyncTest : StringSpec({
"fetches user data" {
val user = fetchUserAsync(1) // suspend function
user.id shouldBe 1
}
})Test bodies are suspend functions; runTest etc. wrappers from kotlinx-coroutines-test work directly.
Step 6 - Data-driven testing
class DataDrivenTest : FunSpec({
context("addition") {
withData(
Triple(1, 2, 3),
Triple(0, 0, 0),
Triple(-1, 1, 0),
) { (a, b, expected) ->
(a + b) shouldBe expected
}
}
})Each row reports as a separate test - failures don't stop subsequent rows.
Step 7 - Isolation modes
Four modes (default SingleInstance); set isolationMode per-spec or globally via AbstractProjectConfig when specs share mutable state. Mode table + example in references/matchers-and-isolation.md.
Step 8 - CI integration
Same as JUnit 5 (Kotest's runner is kotest-runner-junit5):
- run: ./gradlew test jacocoTestReportJaCoCo coverage works identically.
Worked example
Testing Calculator.add with FunSpec + property-based checks:
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Mix multiple spec styles in one project | Reader confusion | Pick one (Step 2) |
| Default isolation + shared mutable state | Tests interfere | InstancePerTest mode (Step 7) |
| Use Kotest property-based + jqwik in same project | Two PB libraries | Pick one |
assertEquals(a, b) (JUnit style) | Mixes paradigms | Use a shouldBe b (Step 3) |
Limitations
References
Kotest - matchers and isolation modes
View source (opens in new window)Kotest - matchers and isolation modes
Matchers
Per kotest.io/docs/assertions/matchers.html (opens in new window):
Core matchers:
| Matcher | Use |
|---|---|
value shouldBe expected | Equality |
value shouldNotBe expected | Inequality |
value should be(expected) | Same; alternate syntax |
value.shouldBeNull() / shouldNotBeNull() | Null check |
string.shouldContain("substring") | String membership |
string.shouldStartWith("prefix") / shouldEndWith("suffix") | String pos |
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 |
Isolation modes
Per kotest.io/docs/framework/isolation-mode.html (opens in new window):
Four modes (default SingleInstance):
| Mode | Behavior |
|---|---|
SingleInstance | One spec instance for all tests (default; fastest) |
InstancePerTest | Fresh spec instance per test (incl. nested contexts) |
InstancePerLeaf | Fresh spec instance per leaf-test only |
Set per-spec:
class StatefulTest : StringSpec({
isolationMode = IsolationMode.InstancePerTest
// ...
})Or globally via AbstractProjectConfig.
Related skills
assertj
Reference for AssertJ - the canonical JVM fluent-assertion library pairable with JUnit 5 / TestNG / Spock; covers the assertThat() entry point, collection matchers (contains, containsExactly, allSatisfy, extracting), exception assertions (assertThatThrownBy, catchThrowable), SoftAssertions for multi-failure collection, recursive comparison (usingRecursiveComparison) for deep equality, and domain-specific custom assertions via AbstractAssert. Use when writing JVM tests that need richer failure messages than built-in assertEquals, or when verifying complex object graphs, exception types, or collections.
junit5-tests
Configures and runs JUnit 5 (Jupiter) - modern JVM testing platform with annotations (`@Test` / `@ParameterizedTest` / `@RepeatedTest` / `@TestFactory`), lifecycle hooks (`@BeforeAll` / `@BeforeEach` / `@AfterEach` / `@AfterAll`), extension model (`@ExtendWith`), display names (`@DisplayName`), conditional execution (`@EnabledOnOs`, `@EnabledIf`), parallel execution config; integrates with Maven Surefire / Gradle test task / IntelliJ. Use when the user works with Java / Kotlin codebases needing the modern JVM standard.
scalatest
Configures and runs ScalaTest - Scala-native test framework with multiple specification styles (FlatSpec, FunSuite, WordSpec, FreeSpec, AsyncFlatSpec for async); Matchers DSL (`should equal`, `should contain`, `shouldBe a [Class]`); integrates with ScalaCheck for property-based testing; sbt + Maven + Gradle support; tagged-test selective execution. Use when working with Scala codebases.
spock-tests
Configures and runs Spock - Groovy-based JVM testing framework with given/when/then BDD blocks, where: data tables for parametrized tests, built-in mocking via Mock()/Stub()/Spy(), interaction-based testing (verify method calls in declarative DSL), implicit assertions in then: blocks. Use when working with Java/Kotlin codebases that benefit from Groovy DSL expressiveness, or maintaining existing Spock projects.
testng-tests
Configures and runs TestNG - JVM testing framework with `@Test` priorities + groups + `dependsOnMethods`; `@DataProvider` for parametrized tests with method-level data sources; `testng.xml` suite definitions for grouping + parallelism config; listeners (`ITestListener`, `ISuiteListener`) for hooks; `ITestContext` for cross-test state; integrates with Maven Surefire / Gradle. Use when working with legacy TestNG codebases or needing TestNG-specific features (test method dependencies, suite-level XML config).