kotlin-test
Enforce TDD workflow for Kotlin. Write Kotest tests first, then implement. Verify 80%+ coverage with Kover.
Kotlin TDD Command
This command enforces test-driven development methodology for Kotlin code using Kotest, MockK, and Kover.
What This Command Does
- Define Types/Interfaces: Scaffold function signatures first
- Write Kotest Tests: Create comprehensive test specs (RED)
- Run Tests: Verify tests fail for the right reason
- Implement Code: Write minimal code to pass (GREEN)
- Refactor: Improve while keeping tests green
- Check Coverage: Ensure 80%+ coverage with Kover
When to Use
Use /kotlin-test when:
- Implementing new Kotlin functions or classes
- Adding test coverage to existing Kotlin code
- Fixing bugs (write failing test first)
- Building critical business logic
- Learning TDD workflow in Kotlin
TDD Cycle
RED -> Write failing Kotest testGREEN -> Implement minimal code to passREFACTOR -> Improve code, tests stay greenREPEAT -> Next test caseExample Session
User: /kotlin-test I need a function to validate user registration requests
Agent:# TDD Session: Registration Validator
## Step 1: Define Interface
```kotlin// validator/RegistrationValidator.ktpackage com.example.validator
data class RegistrationRequest( val name: String, val email: String, val password: String,)
sealed class ValidationResult { data object Valid : ValidationResult() data class Invalid(val errors: List<String>) : ValidationResult()}
fun validateRegistration(request: RegistrationRequest): ValidationResult { TODO("not implemented")}```
## Step 2: Write Kotest Tests (RED)
```kotlin// validator/RegistrationValidatorTest.ktpackage com.example.validator
import io.kotest.core.spec.style.FunSpecimport io.kotest.matchers.shouldBeimport io.kotest.matchers.types.shouldBeInstanceOf
class RegistrationValidatorTest : FunSpec({ test("valid registration returns Valid") { val request = RegistrationRequest( name = "Alice", email = "alice@example.com", password = "SecureP@ss1", )
val result = validateRegistration(request)
result.shouldBeInstanceOf<ValidationResult.Valid>() }
test("blank name returns Invalid") { val request = RegistrationRequest( name = "", email = "alice@example.com", password = "SecureP@ss1", )
val result = validateRegistration(request)
val invalid = result.shouldBeInstanceOf<ValidationResult.Invalid>() invalid.errors shouldBe listOf("Name is required") }
test("invalid email returns Invalid") { val request = RegistrationRequest( name = "Alice", email = "not-an-email", password = "SecureP@ss1", )
val result = validateRegistration(request)
val invalid = result.shouldBeInstanceOf<ValidationResult.Invalid>() invalid.errors shouldBe listOf("Invalid email format") }
test("short password returns Invalid") { val request = RegistrationRequest( name = "Alice", email = "alice@example.com", password = "short", )
val result = validateRegistration(request)
val invalid = result.shouldBeInstanceOf<ValidationResult.Invalid>() invalid.errors shouldBe listOf("Password must be at least 8 characters") }
test("multiple errors returns all errors") { val request = RegistrationRequest( name = "", email = "bad", password = "short", )
val result = validateRegistration(request)
val invalid = result.shouldBeInstanceOf<ValidationResult.Invalid>() invalid.errors.size shouldBe 3 }})```
## Step 3: Run Tests - Verify FAIL
```bash$ ./gradlew test
RegistrationValidatorTest > valid registration returns Valid FAILED kotlin.NotImplementedError: An operation is not implemented
FAILED (5 tests, 0 passed, 5 failed)```
✓ Tests fail as expected (NotImplementedError).
## Step 4: Implement Minimal Code (GREEN)
```kotlin// validator/RegistrationValidator.ktpackage com.example.validator
private val EMAIL_REGEX = Regex("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")private const val MIN_PASSWORD_LENGTH = 8
fun validateRegistration(request: RegistrationRequest): ValidationResult { val errors = buildList { if (request.name.isBlank()) add("Name is required") if (!EMAIL_REGEX.matches(request.email)) add("Invalid email format") if (request.password.length < MIN_PASSWORD_LENGTH) add("Password must be at least $MIN_PASSWORD_LENGTH characters") }
return if (errors.isEmpty()) ValidationResult.Valid else ValidationResult.Invalid(errors)}```
## Step 5: Run Tests - Verify PASS
```bash$ ./gradlew test
RegistrationValidatorTest > valid registration returns Valid PASSEDRegistrationValidatorTest > blank name returns Invalid PASSEDRegistrationValidatorTest > invalid email returns Invalid PASSEDRegistrationValidatorTest > short password returns Invalid PASSEDRegistrationValidatorTest > multiple errors returns all errors PASSED
PASSED (5 tests, 5 passed, 0 failed)```
✓ All tests passing!
## Step 6: Check Coverage
```bash$ ./gradlew koverHtmlReport
Coverage: 100.0% of statements```
✓ Coverage: 100%
## TDD Complete!Test Patterns
StringSpec (Simplest)
class CalculatorTest : StringSpec({ "add two positive numbers" { Calculator.add(2, 3) shouldBe 5 }})BehaviorSpec (BDD)
class OrderServiceTest : BehaviorSpec({ Given("a valid order") { When("placed") { Then("should be confirmed") { /* ... */ } } }})Data-Driven Tests
class ParserTest : FunSpec({ context("valid inputs") { withData("2026-01-15", "2026-12-31", "2000-01-01") { input -> parseDate(input).shouldNotBeNull() } }})Coroutine Testing
class AsyncServiceTest : FunSpec({ test("concurrent fetch completes") { runTest { val result = service.fetchAll() result.shouldNotBeEmpty() } }})Coverage Commands
# Run tests with coverage./gradlew koverHtmlReport
# Verify coverage thresholds./gradlew koverVerify
# XML report for CI./gradlew koverXmlReport
# Open HTML reportopen build/reports/kover/html/index.html
# Run specific test class./gradlew test --tests "com.example.UserServiceTest"
# Run with verbose output./gradlew test --infoCoverage Targets
| Code Type | Target |
|---|---|
| Critical business logic | 100% |
| Public APIs | 90%+ |
| General code | 80%+ |
| Generated code | Exclude |
TDD Best Practices
DO:
- Write test FIRST, before any implementation
- Run tests after each change
- Use Kotest matchers for expressive assertions
- Use MockK’s
coEvery/coVerifyfor suspend functions - Test behavior, not implementation details
- Include edge cases (empty, null, max values)
DON’T:
- Write implementation before tests
- Skip the RED phase
- Test private functions directly
- Use
Thread.sleep()in coroutine tests - Ignore flaky tests
Related Commands
/kotlin-build- Fix build errors/kotlin-review- Review code after implementationverification-loopskill - Run full verification loop
Related
- Skill:
skills/kotlin-testing/ - Skill:
skills/tdd-workflow/