What it is
Kotest is a Kotlin testing framework with multiple spec styles, a large assertion library, property-based testing and data-driven test support.
Choose a spec style — StringSpec, DescribeSpec, BehaviorSpec and others — and use shouldBe style matchers. Property testing generates inputs automatically.
Installation
testImplementation("io.kotest:kotest-runner-junit5:5.9.0")Getting started
The smallest useful thing you can do with it, and what each part means.
kotlin
class BookServiceTest : DescribeSpec({
describe("BookService") {
val repo = mockk<BookRepository>()
val service = BookService(repo)
describe("getTitle") {
it("returns the title when the book exists") {
coEvery { repo.findById(42) } returns Book(42, "Dune")
service.getTitle(42) shouldBe "Dune"
}
it("throws when the book is missing") {
coEvery { repo.findById(99) } returns null
shouldThrow<NotFoundException> { service.getTitle(99) }
}
}
}
})
// Rich matchers
books shouldHaveSize 3
books.shouldContainExactlyInAnyOrder(a, b, c)
result.shouldBeInstanceOf<Success>()
value shouldBeInRange 1..10Advanced usage
Where the library earns its place over a simpler alternative.
kotlin
class SlugifyTest : StringSpec({
"slugs contain only safe characters" {
checkAll(Arb.string()) { input ->
slugify(input).forAll { c -> c.isLetterOrDigit() || c == '-' }
}
}
"round trips" {
checkAll(Arb.int(), Arb.string(1..50)) { id, title ->
val encoded = encode(Book(id, title))
decode(encoded) shouldBe Book(id, title)
}
}
})
class RangeTest : FunSpec({
withData(
nameFn = { "year ${it.first} is ${if (it.second) "valid" else "invalid"}" },
1399 to false, 1400 to true, 2026 to true, 3000 to false,
) { (year, expected) -> isValidYear(year) shouldBe expected }
})Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- No tests are discovered
- The JUnit 5 platform is not enabled. Add useJUnitPlatform() to the Gradle test task.
- Shared state leaks between tests
- Specs are instantiated once per class by default. Set isolationMode to InstancePerLeaf for fresh state per test.
Best practices
- Pick one spec style per project; mixing them makes the suite hard to read.
- Use property tests for pure functions with invariants — encoding, parsing, validation.
- Use withData for table-driven cases instead of near-duplicate test functions.
- Kotest assertions work with JUnit too, if you want the matchers without switching runners.
Background
Why it exists, and what it was reacting to.
Kotest offers what JUnit does not attempt: nested descriptive test structures, powerful matchers and generative testing, all in idiomatic Kotlin.
