kotlin-testing
This Kotlin testing skill provides comprehensive patterns for writing tests using Kotest, MockK, and Kover within a test-driven development workflow. Use it when developing new Kotlin code, adding test coverage to existing implementations, writing property-based tests, or configuring code coverage analysis in Kotlin projects that follow TDD methodology.
git clone --depth 1 https://github.com/affaan-m/ECC /tmp/kotlin-testing && cp -r /tmp/kotlin-testing/.kiro/skills/kotlin-testing ~/.claude/skills/kotlin-testingSKILL.md
# Kotlin Testing Patterns
Comprehensive Kotlin testing patterns for writing reliable, maintainable tests following TDD methodology with Kotest and MockK.
## When to Use
- Writing new Kotlin functions or classes
- Adding test coverage to existing Kotlin code
- Implementing property-based tests
- Following TDD workflow in Kotlin projects
- Configuring Kover for code coverage
## How It Works
1. **Identify target code** — Find the function, class, or module to test
2. **Write a Kotest spec** — Choose a spec style (StringSpec, FunSpec, BehaviorSpec) matching the test scope
3. **Mock dependencies** — Use MockK to isolate the unit under test
4. **Run tests (RED)** — Verify the test fails with the expected error
5. **Implement code (GREEN)** — Write minimal code to pass the test
6. **Refactor** — Improve the implementation while keeping tests green
7. **Check coverage** — Run `./gradlew koverHtmlReport` and verify 80%+ coverage
## Examples
The following sections contain detailed, runnable examples for each testing pattern:
### Quick Reference
- **Kotest specs** — StringSpec, FunSpec, BehaviorSpec, DescribeSpec examples in [Kotest Spec Styles](#kotest-spec-styles)
- **Mocking** — MockK setup, coroutine mocking, argument capture in [MockK](#mockk)
- **TDD walkthrough** — Full RED/GREEN/REFACTOR cycle with EmailValidator in [TDD Workflow for Kotlin](#tdd-workflow-for-kotlin)
- **Coverage** — Kover configuration and commands in [Kover Coverage](#kover-coverage)
- **Ktor testing** — testApplication setup in [Ktor testApplication Testing](#ktor-testapplication-testing)
### TDD Workflow for Kotlin
#### The RED-GREEN-REFACTOR Cycle
```
RED -> Write a failing test first
GREEN -> Write minimal code to pass the test
REFACTOR -> Improve code while keeping tests green
REPEAT -> Continue with next requirement
```
#### Step-by-Step TDD in Kotlin
```kotlin
// Step 1: Define the interface/signature
// EmailValidator.kt
package com.example.validator
fun validateEmail(email: String): Result<String> {
TODO("not implemented")
}
// Step 2: Write failing test (RED)
// EmailValidatorTest.kt
package com.example.validator
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.result.shouldBeFailure
import io.kotest.matchers.result.shouldBeSuccess
class EmailValidatorTest : StringSpec({
"valid email returns success" {
validateEmail("user@example.com").shouldBeSuccess("user@example.com")
}
"empty email returns failure" {
validateEmail("").shouldBeFailure()
}
"email without @ returns failure" {
validateEmail("userexample.com").shouldBeFailure()
}
})
// Step 3: Run tests - verify FAIL
// $ ./gradlew test
// EmailValidatorTest > valid email returns success FAILED
// kotlin.NotImplementedError: An operation is not implemented
// Step 4: Implement minimal code (GREEN)
fun validateEmail(email: String): Result<String> {
if (email.isBlank()) return Result.failure(IllegalArgumentException("Email cannot be blank"))
if ('@' !in email) return Result.failure(IllegalArgumentException("Email must contain @"))
val regex = Regex("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
if (!regex.matches(email)) return Result.failure(IllegalArgumentException("Invalid email format"))
return Result.success(email)
}
// Step 5: Run tests - verify PASS
// $ ./gradlew test
// EmailValidatorTest > valid email returns success PASSED
// EmailValidatorTest > empty email returns failure PASSED
// EmailValidatorTest > email without @ returns failure PASSED
// Step 6: Refactor if needed, verify tests still pass
```
### Kotest Spec Styles
#### StringSpec (Simplest)
```kotlin
class CalculatorTest : StringSpec({
"add two positive numbers" {
Calculator.add(2, 3) shouldBe 5
}
"add negative numbers" {
Calculator.add(-1, -2) shouldBe -3
}
"add zero" {
Calculator.add(0, 5) shouldBe 5
}
})
```
#### FunSpec (JUnit-like)
```kotlin
class UserServiceTest : FunSpec({
val repository = mockk<UserRepository>()
val service = UserService(repository)
test("getUser returns user when found") {
val expected = User(id = "1", name = "Alice")
coEvery { repository.findById("1") } returns expected
val result = service.getUser("1")
result shouldBe expected
}
test("getUser throws when not found") {
coEvery { repository.findById("999") } returns null
shouldThrow<UserNotFoundException> {
service.getUser("999")
}
}
})
```
#### BehaviorSpec (BDD Style)
```kotlin
class OrderServiceTest : BehaviorSpec({
val repository = mockk<OrderRepository>()
val paymentService = mockk<PaymentService>()
val service = OrderService(repository, paymentService)
Given("a valid order request") {
val request = CreateOrderRequest(
userId = "user-1",
items = listOf(OrderItem("product-1", quantity = 2)),
)
When("the order is placed") {
coEvery { paymentService.charge(any()) } returns PaymentResult.Success
coEvery { repository.save(any()) } answers { firstArg() }
val result = service.placeOrder(request)
Then("it should return a confirmed order") {
result.status shouldBe OrderStatus.CONFIRMED
}
Then("it should charge payment") {
coVerify(exactly = 1) { paymentService.charge(any()) }
}
}
When("payment fails") {
coEvery { paymentService.charge(any()) } returns PaymentResult.Declined
Then("it should throw PaymentException") {
shouldThrow<PaymentException> {
service.placeOrder(request)
}
}
}
}
})
```
#### DescribeSpec (RSpec Style)
```kotlin
class UserValidatorTest : DescribeSpec({
describe("validateUser") {
val validator = UserValidator()Workflow command scaffold for add-language-rules in everything-claude-code.
Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.
Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.
>
>
Write articles, guides, blog posts, tutorials, newsletter issues, and other long-form content in a distinctive voice derived from supplied examples or brand guidance. Use when the user wants polished written content longer than a paragraph, especially when voice consistency, structure, and credibility matter.
Patterns and architectures for autonomous Claude Code loops — from simple sequential pipelines to RFC-driven multi-agent DAG systems.
>