kotest
Kotest — flexible, idiomatic Kotlin testing framework. Multiple specification styles (StringSpec, FunSpec, BehaviorSpec, DescribeSpec, FeatureSpec, FreeSpec), rich matcher library, property-based testing, data-driven tests, coroutine support, KMP-friendly. Drop-in alternative or complement to JUnit. USE WHEN: user mentions "Kotest", "io.kotest", "shouldBe", "StringSpec", "BehaviorSpec", "DescribeSpec", "kotest property testing", "Arb.list", "forAll", "kotest matchers", "kotlin tests" DO NOT USE FOR: JUnit-specific patterns - use junit skill (or framework-specific test skills) DO NOT USE FOR: Flow testing - use `testing/turbine` DO NOT USE FOR: Compose snapshot tests - use `testing/compose-snapshot` DO NOT USE FOR: Mobile E2E - use `testing/maestro`
What this skill does
# Kotest
> **Deep Knowledge**: Use `mcp__documentation__fetch_docs` with technology: `kotest`.
## Why Kotest
| Feature | Kotest | JUnit 5 | Spek |
|---|---|---|---|
| Spec styles (BDD, FunSpec, StringSpec, etc.) | ✅ 9 styles | ❌ Single | ✅ |
| Rich matchers (`shouldBe`, `shouldContain`, `shouldThrow`) | ✅ Built-in | ❌ Need AssertJ/Hamcrest | ✅ |
| Property-based testing | ✅ Native | ❌ External | ❌ |
| Data-driven tests | ✅ `withData` | ✅ `@ParameterizedTest` | ❌ |
| Coroutine native (`runTest` via `coroutineScope`) | ✅ | ✅ Manual | ❌ |
| KMP support | ✅ Full | Partial (JVM only) | ❌ |
| Lifecycle hooks | ✅ Many | ✅ | ✅ |
| Test isolation modes | ✅ Configurable | ❌ Per-method | ❌ |
| Plugin ecosystem | ✅ Spring, Allure, Koin, MockK | ✅ | Limited |
## Setup
```kotlin
// build.gradle.kts
plugins {
id("io.kotest.multiplatform") version "5.9.1" // for KMP
// OR
kotlin("jvm") // JVM-only
}
dependencies {
testImplementation("io.kotest:kotest-runner-junit5:5.9.1") // JVM
testImplementation("io.kotest:kotest-assertions-core:5.9.1")
testImplementation("io.kotest:kotest-property:5.9.1") // property testing
testImplementation("io.kotest:kotest-framework-datatest:5.9.1") // data-driven
testImplementation("io.kotest.extensions:kotest-extensions-koin:1.3.0") // Koin plugin
}
// KMP (commonTest)
kotlin {
sourceSets.commonTest.dependencies {
implementation("io.kotest:kotest-framework-engine:5.9.1")
implementation("io.kotest:kotest-assertions-core:5.9.1")
implementation("io.kotest:kotest-property:5.9.1")
}
}
tasks.withType<Test> {
useJUnitPlatform() // for JVM Kotest runs via JUnit Platform
}
```
## Spec Styles
### StringSpec (most idiomatic)
```kotlin
class WalletTest : StringSpec({
"valid mnemonic creates wallet" {
val wallet = Wallet.fromMnemonic("abandon abandon ...")
wallet.address(0) shouldStartWith "bc1q"
}
"invalid mnemonic throws" {
shouldThrow<InvalidMnemonicException> {
Wallet.fromMnemonic("not enough words")
}
}
})
```
### FunSpec
```kotlin
class WalletTest : FunSpec({
test("valid mnemonic creates wallet") {
val wallet = Wallet.fromMnemonic(...)
wallet.address(0) shouldStartWith "bc1q"
}
context("when balance is zero") {
test("send fails") {
shouldThrow<InsufficientFundsException> { wallet.send(...) }
}
}
})
```
### BehaviorSpec (BDD)
```kotlin
class WalletBehavior : BehaviorSpec({
given("an empty wallet") {
val wallet = Wallet.empty()
`when`("sending 1000 sats") {
then("throws InsufficientFundsException") {
shouldThrow<InsufficientFundsException> { wallet.send(1000) }
}
}
}
given("a wallet with 5000 sats") {
val wallet = Wallet.withBalance(5000)
`when`("sending 1000 sats") {
val result = wallet.send(1000)
then("balance becomes 4000") { wallet.balance shouldBe 4000 }
then("returns success") { result.isSuccess shouldBe true }
}
}
})
```
### DescribeSpec
```kotlin
class WalletDescribe : DescribeSpec({
describe("Wallet") {
describe("send()") {
it("succeeds when sufficient funds") { /* ... */ }
it("fails on insufficient funds") { /* ... */ }
}
}
})
```
### FreeSpec (deeply nested)
```kotlin
class WalletFree : FreeSpec({
"Wallet" - {
"send" - {
"with sufficient funds" - {
"succeeds" {
val wallet = Wallet.withBalance(5000)
wallet.send(1000).isSuccess shouldBe true
}
}
}
}
})
```
Pick **StringSpec** for most cases. Use **BehaviorSpec/DescribeSpec** when stakeholders read tests. **FunSpec** for nested setup with `context`.
## Matchers
```kotlin
// Equality
result shouldBe 42
result shouldNotBe 0
list shouldContainExactly listOf(1, 2, 3)
list shouldContainExactlyInAnyOrder listOf(3, 1, 2)
list shouldContain 5
list shouldHaveSize 3
// Strings
"hello world" shouldContain "world"
"abc" shouldStartWith "a"
"abc" shouldEndWith "c"
"abc" shouldMatch Regex("[a-z]+")
"abc" shouldHaveLength 3
// Collections
emptyList<Int>().shouldBeEmpty()
listOf(1, 2, 3).shouldContainAll(1, 2)
mapOf("a" to 1).shouldContainKey("a")
// Null
foo.shouldBeNull()
bar.shouldNotBeNull()
// Type
result.shouldBeInstanceOf<Success>()
result.shouldBeTypeOf<List<String>>()
// Booleans
flag.shouldBeTrue()
flag.shouldBeFalse()
// Numeric
balance shouldBeGreaterThan 0
balance shouldBeBetween 1000 to 5000
amount.toDouble() shouldBe (1.5 plusOrMinus 0.01)
// Throwable
shouldThrow<IllegalArgumentException> { invalid() }
val ex = shouldThrow<WalletException> { send() }
ex.message shouldContain "insufficient"
// Exact type
shouldThrowExactly<InsufficientFundsException> { /* not subclasses */ }
// Any matcher works in negative
result shouldNotBe null
"abc" shouldNotContain "xyz"
```
Inverse via `shouldNot`:
```kotlin
list shouldNot contain(0)
"abc" shouldNot startWith("z")
```
## Data-Driven Tests (`withData`)
```kotlin
class UriParserTest : FunSpec({
context("BIP21 parser") {
withData(
"bitcoin:bc1q..." to BitcoinUri(address = "bc1q..."),
"bitcoin:bc1q...?amount=0.1" to BitcoinUri(address = "bc1q...", amount = 10_000_000),
"bitcoin:bc1q...?label=Test" to BitcoinUri(address = "bc1q...", label = "Test"),
) { (input, expected) ->
BitcoinUri.parse(input) shouldBe expected
}
}
})
```
For named cases:
```kotlin
withData(
nameFn = { "parses ${it.first} → ${it.second}" },
"bitcoin:bc1q..." to BitcoinUri(...),
"bitcoin:bc1q...?amount=0.1" to BitcoinUri(...),
) { (input, expected) ->
BitcoinUri.parse(input) shouldBe expected
}
```
For test class data:
```kotlin
data class TestCase(val input: String, val expected: BitcoinUri)
withData(
TestCase("bitcoin:bc1q...", BitcoinUri(...)),
TestCase("bitcoin:bc1q...?amount=0.1", BitcoinUri(...)),
) { (input, expected) ->
BitcoinUri.parse(input) shouldBe expected
}
```
## Property-Based Testing
```kotlin
import io.kotest.property.*
import io.kotest.property.arbitrary.*
class FeeCalculatorTest : StringSpec({
"fee is always at least 1 sat/vbyte" {
forAll<Int>(Arb.int(0..1_000_000)) { weight ->
val fee = calculateFee(weight, feeRate = 1)
fee >= weight / 4
}
}
"fee scales linearly" {
checkAll<Int, Int>(
Arb.int(100..10_000), // weight
Arb.int(1..100), // fee rate
) { weight, rate ->
val fee = calculateFee(weight, rate)
fee shouldBe (weight / 4 * rate)
}
}
})
```
### Arbitraries
```kotlin
Arb.int(0..100)
Arb.long()
Arb.double(0.0..1.0)
Arb.string(minSize = 1, maxSize = 100)
Arb.string(8..12, Codepoint.alphanumeric())
Arb.list(Arb.int(), 0..10)
Arb.set(Arb.string())
Arb.map(Arb.string(), Arb.int(), 0..10)
// Custom
data class User(val id: Long, val name: String)
val userArb = arbitrary {
User(
id = Arb.long().bind(),
name = Arb.string(1..50).bind(),
)
}
forAll<User>(userArb) { user ->
user.id != 0L
}
```
### Shrinking
When a property fails, Kotest **shrinks** the input to find the smallest failing case automatically. Reported in test output.
## Lifecycle Hooks
```kotlin
class WalletTest : StringSpec({
beforeSpec {
// Once before all tests in spec
Db.migrate()
}
beforeTest {
// Before each test
Db.clear()
}
afterTest { (test, result) ->
// After each test
if (result.isError) takeDebugSnapshot(test.name.testName)
}
afterSpec {
Db.close()
}
"test 1" { /* ... */ }
"test 2" { /* Related in Code Review
gstack
IncludedFast headless browser for QA testing and site dogfooding. Navigate pages, interact with elements, verify state, diff before/after, take annotated screenshots, test responsive layouts, forms, uploads, dialogs, and capture bug evidence. Use when asked to open or test a site, verify a deployment, dogfood a user flow, or file a bug with screenshots. (gstack)
startup-due-diligence
IncludedLegal due diligence review for seed-stage and Series A startups (US, Delaware C-Corp focus). Supports both investor and founder perspectives. Capabilities include: (1) Interactive document review and issue spotting; (2) Document request list generation; (3) Cap table and SAFE/convertible note analysis; (4) Red flag identification with severity ratings; (5) Diligence report generation. TRIGGERS: due diligence, DD, startup investment, cap table review, Series A, seed round, investor diligence, legal review startup, SAFE analysis, convertible note, 409A, founder vesting.
interview-master
IncludedThis skill should be used when the user asks to "generate interview questions", "prepare for interview", "optimize resume", "conduct mock interview", "analyze git commits for resume", "generate resume from code", "review my resume", or mentions interview preparation, career assistance, or extracting project experience from git history. Provides comprehensive interview and career development guidance for both job seekers and interviewers.
fix-issue
IncludedFixes GitHub issues using parallel analysis agents for root cause investigation, code exploration, and regression detection. Reads issue context from gh CLI, searches codebase and memory for related patterns, generates a fix with tests, and links the resolution back to the issue via PR. Includes prevention analysis to avoid recurrence. Use when debugging errors, resolving regressions, fixing bugs, or triaging issues.
sf-apex
IncludedGenerates and reviews Salesforce Apex code with 150-point scoring. TRIGGER when: user writes, reviews, or fixes Apex classes, triggers, test classes, batch/queueable/schedulable jobs, or touches .cls/.trigger files. DO NOT TRIGGER when: LWC JavaScript (use sf-lwc), Flow XML (use sf-flow), SOQL-only queries (use sf-soql), or non-Salesforce code.
swift-development
IncludedComprehensive Swift development for building, testing, and deploying iOS/macOS applications. Use when Claude needs to: (1) Build Swift packages or Xcode projects from command line, (2) Run tests with XCTest or Swift Testing framework, (3) Manage iOS simulators with simctl, (4) Handle code signing, provisioning profiles, and app distribution, (5) Format or lint Swift code with SwiftFormat/SwiftLint, (6) Work with Swift Package Manager (SPM), (7) Implement Swift 6 concurrency patterns (async/await, actors, Sendable), (8) Create SwiftUI views with MVVM architecture, (9) Set up Core Data or SwiftData persistence, or any other Swift/iOS/macOS development tasks.