Kotest
Organizationkotest
Write, review, migrate, and debug Kotlin tests with Kotest 6.x: spec styles (FunSpec, DescribeSpec, BehaviorSpec, StringSpec, ...), matchers (shouldBe, shouldThrow, assertSoftly, eventually), data-driven tests (withTests/withData), property-based testing (Arb, checkAll, forAll, permutations), lifecycle hooks, extensions (Spring, Testcontainers, Koin), ProjectConfig, coroutine/virtual-time testing, tags, and Kotlin Multiplatform setup. Use when a project depends on io.kotest, when editing *Spec/*Test.kt files that import io.kotest, when the user mentions Kotest, kotest matchers, Arb or property testing in Kotlin, or when migrating from JUnit 4/5, TestNG, or Spek to Kotest.
Overview
- Repository
- kotest/kotest
- Publisher
- kotest
- Stars
- 4.8K
- Forks
- 705
- Bundled files
- 10
- License
- Apache-2.0
- Updated
- Nov 14, 2026
Installation
tmp=$(mktemp -d)
git clone --depth 1 https://github.com/kotest/kotest.git "$tmp"
mkdir -p ~/.claude/skills
cp -R "$tmp/skills/kotest" ~/.claude/skills/kotest
rm -rf "$tmp"tmp=$(mktemp -d)
git clone --depth 1 https://github.com/kotest/kotest.git "$tmp"
mkdir -p ~/.codex/skills
cp -R "$tmp/skills/kotest" ~/.codex/skills/kotest
rm -rf "$tmp"Install the Kotest agent skill from https://github.com/kotest/kotest/tree/master/skills/kotest into this project's skills folder.Instructions
Kotest
This skill covers Kotest 6.2. Kotest has three parts that can be used independently: the test framework, the assertions library (which works under any framework, including JUnit), and property testing.
Step 0: Read the Project Before Writing Anything
- Find the Kotest version in
build.gradle.ktsorgradle/libs.versions.toml.- 6.x needs Kotlin 2.2+ and JDK 11+.
- On 5.x many APIs below differ, so check the project's existing tests before using them.
- Find the targets: JVM-only, Android, or KMP (
kotlin("multiplatform"),commonTest). - Match the existing tests: same spec style, package (
io.kotestorcom.sksamuel.kotestin old code), indentation, and helpers. Don't introduce a second spec style into a module. - Look for an existing
ProjectConfig/PackageConfigand any registered extensions.
With Bash available, scripts/analyze-project.sh [dir] prints a summary of all of this.
Pick the Path
| Task | Read |
|---|---|
| New project / add Kotest to a build | SETUP-REFERENCE.md |
| Write tests in a style | SPEC-STYLES.md |
| Choose a matcher, custom matchers, soft assertions | ASSERTIONS-REFERENCE.md |
| Data-driven tests, config, coroutines, tags, extensions | FRAMEWORK-REFERENCE.md |
| Property-based testing | PROPERTY-TESTING-REFERENCE.md |
| Migrate from JUnit / TestNG / Spek | MIGRATION-GUIDE.md |
| Tests not running, config ignored, hangs | KOTEST-DEBUGGING.md, KNOWN-ISSUES.md |
Load only the reference you need.
Minimal Setup
// JVM — build.gradle.kts
dependencies {
testImplementation("io.kotest:kotest-runner-junit5:<version>")
testImplementation("io.kotest:kotest-assertions-core:<version>")
testImplementation("io.kotest:kotest-property:<version>") // if needed
}
tasks.withType<Test>().configureEach { useJUnitPlatform() }For KMP, apply id("com.google.devtools.ksp") and id("io.kotest"), then put kotest-framework-engine and
kotest-assertions-core in commonTest. Add kotest-runner-junit5 and useJUnitPlatform() for the JVM target.
See SETUP-REFERENCE for JS, Wasm, Native, and Android.
Writing Good Kotest Tests
import io.kotest.assertions.assertSoftly
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.datatest.withTests
import io.kotest.matchers.collections.shouldContainExactly
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.shouldBeInstanceOf
class OrderServiceTest : FunSpec({
val repo = InMemoryOrderRepo()
val service = OrderService(repo)
beforeEach { repo.clear() }
context("placing an order") {
test("stores the order with its lines") {
val order = service.place(cart("apple", "pear"))
assertSoftly(repo.find(order.id)) {
status shouldBe Status.PLACED
lines.map { it.sku } shouldContainExactly listOf("apple", "pear")
}
}
test("rejects an empty cart") {
val e = shouldThrow<IllegalArgumentException> { service.place(cart()) }
e.message shouldContain "empty"
}
withTests(-1, 0) { qty ->
shouldThrow<IllegalArgumentException> { service.place(cart("apple" to qty)) }
}
}
test("returns a typed result") {
val result = service.lookup("A-1").shouldBeInstanceOf<Found>() // smart-casts and returns Found
withClue("lookup A-1") { result.order.id shouldBe "A-1" }
}
})Rules:
- Use the most specific matcher. Write
list.shouldContain(x),str.shouldStartWith("a"),n shouldBeGreaterThan 0rather than(cond) shouldBe true, so the failure message carries the actual values. - Use the returned value from the matcher.
shouldThrow,shouldNotBeNull,shouldBeInstanceOf,shouldBeSingle, andshouldBeSuccessreturn their subject, so you don't need!!or casts. - Test bodies are
suspend. Never wrap them inrunBlocking. For delays, usecoroutineTestScope = trueand virtual time. - Wait with
eventually, notThread.sleep. Writeeventually(5.seconds) { ... }to poll for an asynchronous result, andcontinuallyto assert that something stays true. - Group related assertions with
assertSoftlyso one run reports every failure. UsewithClueorasClueto add context. - Use data-driven tests instead of loops or copy-paste. In FunSpec and FreeSpec use
withTestsfor leaf tests andwithContextsfor groups. Each style has its own variant; see FRAMEWORK-REFERENCE. - Use property tests for invariants.
checkAll(Arb.int(), Arb.int()) { a, b -> (a + b) shouldBe (b + a) }. - Make tests independent. If shared mutable spec fields leak between tests, reset them in
beforeEachor useisolationMode = IsolationMode.InstancePerRoot. - Name tests by behaviour, for example
"rejects an empty cart", not"test1". Names must be unique within a scope.
6.x Facts Agents Commonly Get Wrong
| Wrong (5.x / hallucinated) | Correct (6.2) |
|---|---|
kotest-framework-datatest dependency |
Built into the engine; import io.kotest.datatest.* |
withData as the only data-test function |
withTests / withContexts / per-style variants (withIts, withShoulds, …) |
specConcurrencyMode, testConcurrencyMode, parallelism |
specExecutionMode = SpecExecutionMode.Concurrent, testExecutionMode (JVM only) |
./gradlew test -Dkotest.tags=... |
KOTEST_TAGS="A & !B" ./gradlew test, or systemProperty(...) on the Test task |
@AutoScan extensions |
Removed; register via extension(...) or ProjectConfig.extensions |
IsolationMode.InstancePerTest / InstancePerLeaf |
IsolationMode.InstancePerRoot |
TestContainerExtension(...) / ContainerExtension(...) |
install(TestContainerSpecExtension(container)) (old ones removed in 6.3) |
forAll(row(...)) table tests from core |
kotest-assertions-table (io.kotest.data), or prefer data-driven tests |
import io.kotest.assertions.nondeterministic.retry |
import io.kotest.assertions.retry; retry(maxRetry, timeout, delay) |
object ProjectConfig everywhere |
class ProjectConfig : AbstractProjectConfig() (Native requires a class) |
kotest-assertions-api |
kotest-assertions-shared |
Constructor-injected Spring spec using extension(SpringExtension()) |
@ApplyExtension(SpringExtension::class) on the class |
Recent Additions Worth Using (6.1 – 6.2)
a shouldEqual b(io.kotest.matchers.equals): a type-safeshouldBe, so1 shouldEqual "1"doesn't compile.actual withEqs { register<BigDecimal>(myEq) } shouldBe expected(io.kotest.assertions.eq), wheremyEqis your ownEq<BigDecimal>: per-assertion equality overrides that apply recursively into collections and data classes, and are safe under concurrency (unlike the globalDefaultEqResolver.register).shouldBeEqualUsingFields { excludedProperties = setOf(Foo::updatedAt); expected }(JVM): field-by-field diffs.list.shouldBeSingle()returns the element;result.shouldBeFailure<IOException> { };x.shouldBeInstanceOf(Foo::class)returnsFoo.DefaultTestConfig,PackageConfig, andfailfastgive layered configuration.coroutineTestScope = truewithtestCoroutineSchedulergives virtual time. On Kotest ≤ 6.2.6 also addkotlinx-coroutines-testto test deps to call scheduler methods.- Permutations DSL (
kotest-property-permutations, experimental) is a property-testing alternative. - In the Gradle plugin
kotest { }block:kotestVersion(aligns artifact versions),enablePowerAssert, andalwaysRerunTests. - Custom spec styles via
AbstractSpec+@TestRunnable.
Run and Verify
./gradlew test # JVM
./gradlew test --tests "com.example.OrderServiceTest" # one spec
./gradlew allTests # KMP: every target (or jvmTest, jsTest, linuxX64Test, ...)
KOTEST_DEBUG=true ./gradlew test --info # engine debug output (see KOTEST-DEBUGGING)After writing or migrating tests, run them and confirm they were discovered: the report shows a non-zero
count, and "0 tests" usually means a missing useJUnitPlatform() or KSP plugin. If tests pass on the first
run, deliberately break one assertion to check that it can fail. For full setup or migration work, go
through assets/checklist.md.
Bundled files
assets/checklist.md3 KBreferences/ASSERTIONS-REFERENCE.md13 KBreferences/FRAMEWORK-REFERENCE.md15 KBreferences/KNOWN-ISSUES.md9 KBreferences/KOTEST-DEBUGGING.md5 KBreferences/MIGRATION-GUIDE.md8 KBreferences/PROPERTY-TESTING-REFERENCE.md10 KBreferences/SETUP-REFERENCE.md8 KBreferences/SPEC-STYLES.md8 KBscripts/analyze-project.sh10 KB
FAQ
What does Kotest do?
Write, review, migrate, and debug Kotlin tests with Kotest 6.x: spec styles (FunSpec, DescribeSpec, BehaviorSpec, StringSpec, ...), matchers (shouldBe, shouldThrow, assertSoftly, eventually), data-driven tests (withTests/withData), property-based testing (Arb, checkAll, forAll, permutations), lifecycle hooks, extensions (Spring, Testcontainers, Koin), ProjectConfig, coroutine/virtual-time testing, tags, and Kotlin Multiplatform setup. Use when a project depends on io.kotest, when editing *Spec/*Test.kt files that import io.kotest, when the user mentions Kotest, kotest matchers, Arb or property testing in Kotlin, or when migrating from JUnit 4/5, TestNG, or Spek to Kotest.
Who made Kotest?
kotest (an organization) publishes it in the kotest/kotest repository on GitHub.
Is Kotest free?
Yes. Kotest is open source, published by kotest under the Apache-2.0 license.
What comes with Kotest?
Its instructions and 10 files the agent reads only when a task needs them, such as references, examples or scripts.
Which agents can use Kotest?
Any coding agent that reads SKILL.md files, including Claude Code, Codex, Cursor, GitHub Copilot and Gemini CLI. The agent loads the skill when a task matches its description.
What are agent skills?
An agent skill is a folder with a SKILL.md file: instructions that teach a coding agent how to do one kind of task, and optionally files it can read while it works. The agent keeps only the skill's name and description in mind and reads the rest when a task calls for it.
How are agent skills different from MCP servers?
An MCP server gives an agent new tools to call, code that runs somewhere and returns results. A skill gives it knowledge: how to approach a task with the tools it already has. Many agents use both.
How can my agent test my Android app?
Robomori lets coding agents create, start and control Android emulators and devices from one command, so they can install your app, take screenshots and check dark mode, font sizes and languages.