Claude
Skills
Sign in
Back

kotlin-testing

Included with Lifetime
$97 forever

Kotest, MockK, coroutine testi, property-based testing ve Kover coverage ile Kotlin test kalıpları. İdiomatic Kotlin uygulamalarıyla TDD metodolojisini takip eder.

Code Review

What this skill does


# Kotlin Test Kalıpları

Kotest ve MockK ile TDD metodolojisini takip ederek güvenilir, sürdürülebilir testler yazmak için kapsamlı Kotlin test kalıpları.

## Ne Zaman Kullanılır

- Yeni Kotlin fonksiyonları veya class'lar yazarken
- Mevcut Kotlin koduna test coverage eklerken
- Property-based testler uygularken
- Kotlin projelerinde TDD iş akışını takip ederken
- Kod coverage için Kover yapılandırırken

## Nasıl Çalışır

1. **Hedef kodu belirle** — Test edilecek fonksiyon, class veya modülü bul
2. **Kotest spec yaz** — Test scope'una uygun bir spec stili seç (StringSpec, FunSpec, BehaviorSpec)
3. **Bağımlılıkları mock'la** — Test edilen birimi izole etmek için MockK kullan
4. **Testleri çalıştır (RED)** — Testin beklenen hatayla başarısız olduğunu doğrula
5. **Kodu uygula (GREEN)** — Testi geçmek için minimal kod yaz
6. **Refactor** — Testleri yeşil tutarken implementasyonu iyileştir
7. **Coverage'ı kontrol et** — `./gradlew koverHtmlReport` çalıştır ve %80+ coverage'ı doğrula

## TDD İş Akışı for Kotlin

### RED-GREEN-REFACTOR Döngüsü

```
RED     -> Önce başarısız bir test yaz
GREEN   -> Testi geçmek için minimal kod yaz
REFACTOR -> Testleri yeşil tutarken kodu iyileştir
REPEAT  -> Sonraki gereksinimle devam et
```

### Kotlin'de Adım Adım TDD

```kotlin
// Adım 1: Interface/signature tanımla
// EmailValidator.kt
package com.example.validator

fun validateEmail(email: String): Result<String> {
    TODO("not implemented")
}

// Adım 2: Başarısız test yaz (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("[email protected]").shouldBeSuccess("[email protected]")
    }

    "empty email returns failure" {
        validateEmail("").shouldBeFailure()
    }

    "email without @ returns failure" {
        validateEmail("userexample.com").shouldBeFailure()
    }
})

// Adım 3: Testleri çalıştır - FAIL doğrula
// $ ./gradlew test
// EmailValidatorTest > valid email returns success FAILED
//   kotlin.NotImplementedError: An operation is not implemented

// Adım 4: Minimal kodu uygula (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)
}

// Adım 5: Testleri çalıştır - PASS doğrula
// $ ./gradlew test
// EmailValidatorTest > valid email returns success PASSED
// EmailValidatorTest > empty email returns failure PASSED
// EmailValidatorTest > email without @ returns failure PASSED

// Adım 6: Gerekirse refactor et, testlerin hala geçtiğini doğrula
```

## Kotest Spec Stilleri

### StringSpec (En Basit)

```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 benzeri)

```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 Stili)

```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)
                }
            }
        }
    }
})
```

## Kotest Matcher'lar

### Temel Matcher'lar

```kotlin
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.*
import io.kotest.matchers.collections.*
import io.kotest.matchers.nulls.*

// Eşitlik
result shouldBe expected
result shouldNotBe unexpected

// String'ler
name shouldStartWith "Al"
name shouldEndWith "ice"
name shouldContain "lic"
name shouldMatch Regex("[A-Z][a-z]+")
name.shouldBeBlank()

// Koleksiyonlar
list shouldContain "item"
list shouldHaveSize 3
list.shouldBeSorted()
list.shouldContainAll("a", "b", "c")
list.shouldBeEmpty()

// Null'lar
result.shouldNotBeNull()
result.shouldBeNull()

// Tipler
result.shouldBeInstanceOf<User>()

// Sayılar
count shouldBeGreaterThan 0
price shouldBeInRange 1.0..100.0

// Exception'lar
shouldThrow<IllegalArgumentException> {
    validateAge(-1)
}.message shouldBe "Age must be positive"

shouldNotThrow<Exception> {
    validateAge(25)
}
```

## MockK

### Temel Mocking

```kotlin
class UserServiceTest : FunSpec({
    val repository = mockk<UserRepository>()
    val logger = mockk<Logger>(relaxed = true) // Relaxed: varsayılanları döndürür
    val service = UserService(repository, logger)

    beforeTest {
        clearMocks(repository, logger)
    }

    test("findUser delegates to repository") {
        val expected = User(id = "1", name = "Alice")
        every { repository.findById("1") } returns expected

        val result = service.findUser("1")

        result shouldBe expected
        verify(exactly = 1) { repository.findById("1") }
    }

    test("findUser returns null for unknown id") {
        every { repository.findById(any()) } returns null

        val result = service.findUser("unknown")

        result.shouldBeNull()
    }
})
```

### Coroutine Mocking

```kotlin
class AsyncUserServiceTest : FunSpec({
    val repository = mockk<UserRepository>()
    val service = UserService(repository)

    test("getUser suspending function") {
        coEvery { repository.findById("1") } returns User(id = "1", name = "Alice")

        val result = service.getUser("1")

        result.name shouldBe "Alice"
        coVerify { repository.findById("1") }
    }

    test("getUser with delay") {
        coEvery { repository.findById("1") } coAnswers {
            delay(100) // Async çalışmayı simüle et
            User(id = "1", name = "Alice")
        }

        val result = service.getUser("1")
        result.name shouldBe "Alice"
    }
})
```

## Coroutine Testi

### Suspend Fonksiyonlar İçin runTest

```kotlin
import kotlinx.coroutines.test.runTest

class CoroutineServiceTest : FunSpec({
    test("concurrent fetches complete together") {
        runTest {
            val service = DataService

Related in Code Review