affaan-m/ECC

kotlin-testing

Patrones de pruebas Kotlin con Kotest, MockK, pruebas de coroutines, pruebas basadas en propiedades y cobertura con Kover. Sigue la metodología TDD con prácticas idiomáticas de Kotlin.

80Collecting
See how to use itView GitHub source
npx skills add https://github.com/affaan-m/ECC --skill "docs/es/skills/kotlin-testing"
Automated source guide

Source checked Jul 28, 2026·Refresh due Oct 26, 2026

Reorganized from the pinned upstream SKILL.md

Turn kotlin-testing's source instructions into a guide you can follow

According to the pinned SKILL.md from affaan-m/ECC: Patrones completos de pruebas Kotlin para escribir pruebas confiables y mantenibles siguiendo la metodología TDD con Kotest y MockK.

npx skills add https://github.com/affaan-m/ECC --skill "docs/es/skills/kotlin-testing"
Check the pinned source

Best fit

  • Patrones de pruebas Kotlin con Kotest, MockK, pruebas de coroutines, pruebas basadas en propiedades y cobertura con Kover. Sigue la metodología TDD con prácticas idiomáticas de Kotlin.

Bring this context

  • A concrete task that matches the documented purpose of kotlin-testing.
  • The files, examples, or context the task depends on.
  • Your constraints, target environment, and definition of done.

Expected outputs

  • A result that follows the pinned kotlin-testing instructions.
  • A concise record of assumptions, inputs used, and unresolved questions.
  • A final check against the source workflow and relevant permission signals.

Key source sections

Read kotlin-testing through these 5 source sections

Sections are extracted automatically from the pinned SKILL.md and link back to the source.

01

Cuándo Usar

Escribir nuevas funciones o clases Kotlin

SKILL.md · Cuándo Usar
Escribir nuevas funciones o clases KotlinAgregar cobertura de pruebas a código Kotlin existenteImplementar pruebas basadas en propiedades
02

Cómo Funciona

1. Identificar el código objetivo — Encontrar la función, clase o módulo a probar 2. Escribir un spec Kotest — Elegir un estilo de spec (StringSpec, FunSpec, BehaviorSpec) acorde al alcance de la prueba 3. Mockear dependencias — Usar MockK para aislar la unidad bajo prueba 4. Ej…

SKILL.md · Cómo Funciona
Identificar el código objetivo — Encontrar la función, clase o módulo a probarEscribir un spec Kotest — Elegir un estilo de spec (StringSpec, FunSpec, BehaviorSpec) acorde al alcance de la pruebaMockear dependencias — Usar MockK para aislar la unidad bajo prueba
03

Ejemplos

Las siguientes secciones contienen ejemplos detallados y ejecutables para cada patrón de prueba:

SKILL.md · Ejemplos
Specs Kotest — Ejemplos de StringSpec, FunSpec, BehaviorSpec, DescribeSpec en Estilos de Spec KotestMocking — Configuración de MockK, mocking de coroutines, captura de argumentos en MockKFlujo de trabajo TDD — Ciclo RED/GREEN/REFACTOR completo con EmailValidator en Flujo de Trabajo TDD para Kotlin
04

Referencia Rápida

Specs Kotest — Ejemplos de StringSpec, FunSpec, BehaviorSpec, DescribeSpec en Estilos de Spec Kotest

SKILL.md · Referencia Rápida
Specs Kotest — Ejemplos de StringSpec, FunSpec, BehaviorSpec, DescribeSpec en Estilos de Spec KotestMocking — Configuración de MockK, mocking de coroutines, captura de argumentos en MockKFlujo de trabajo TDD — Ciclo RED/GREEN/REFACTOR completo con EmailValidator en Flujo de Trabajo TDD para Kotlin
05

Flujo de Trabajo TDD para Kotlin

Review the “Flujo de Trabajo TDD para Kotlin” section in the pinned source before continuing.

SKILL.md · Flujo de Trabajo TDD para Kotlin
Review and apply the “Flujo de Trabajo TDD para Kotlin” source section.

SkillSignal prompt templates

Provide the task, context, and acceptance criteria

These prompts were written by SkillSignal from the source structure; they are not upstream text.

Task-start prompt

Confirm source fit, inputs, and outputs before acting.

Use kotlin-testing to help me with: [specific task]. Context: [files, data, or background]. Constraints: [environment, scope, and prohibited actions]. Before acting, check the pinned SKILL.md and explain which sections apply, what inputs are still missing, and what you will deliver.

Source-guided execution

Make the Agent explicitly follow the key extracted sections.

Apply the pinned kotlin-testing source to [task]. Pay particular attention to these source sections: “Cuándo Usar”, “Cómo Funciona”, “Ejemplos”, “Referencia Rápida”, “Flujo de Trabajo TDD para Kotlin”. Preserve the important decision at each step. Mark facts not covered by the source as “needs confirmation” instead of inventing them. Then verify the result against my acceptance criteria: [criteria].

Result-review prompt

Check omissions, permissions, and source drift before delivery.

Review the current kotlin-testing result: (1) does it satisfy the original task; (2) were any applicable steps or limits in the pinned SKILL.md missed; (3) did it perform any unauthorized file, command, network, or data action; and (4) which conclusions remain unverified? List issues first, then fix only what the source or user authorization supports.

Output checklist

Verify each item before delivery

The task matches the purpose documented in the SKILL.md.

The source section “Cuándo Usar” has been checked.

The source section “Cómo Funciona” has been checked.

The source section “Ejemplos” has been checked.

The source section “Referencia Rápida” has been checked.

Inputs, constraints, and acceptance criteria are explicit.

Unverified facts, compatibility, and outcome claims are clearly marked.

Any file, command, network, or data action has been reviewed.

Choose a different workflow

When another Skill is the better fit

FAQ

What does kotlin-testing do?

Patrones completos de pruebas Kotlin para escribir pruebas confiables y mantenibles siguiendo la metodología TDD con Kotest y MockK.

How do I start using kotlin-testing?

The catalog detected this source-specific install command: npx skills add https://github.com/affaan-m/ECC --skill "docs/es/skills/kotlin-testing". Inspect the command and pinned source before running it.

Which Agent platforms does it declare?

No dedicated Agent platform is declared in the pinned source record.

Repository stars
234,327
Repository forks
35,711
Quality
80/100
Source repository last pushed

Quality breakdown

Based on traceable docs and repository signals; stars are not treated as quality.

80/100
Documentation30/30
Specificity18/25
Maintenance20/20
Trust signals12/25

Compare before choosing

Related Agent Skills and source variants

These links are selected from shared tasks, functions, stacks, platforms, and same-name variants. Compare the source owner, documentation, permissions, and maintenance signals.

View original Skill.mdThis page is parsed directly from the repository SKILL.md without editorial rewriting. Collected: Jul 28, 2026 · about 3 min

Patrones de Pruebas Kotlin

Patrones completos de pruebas Kotlin para escribir pruebas confiables y mantenibles siguiendo la metodología TDD con Kotest y MockK.

Cuándo Usar

  • Escribir nuevas funciones o clases Kotlin
  • Agregar cobertura de pruebas a código Kotlin existente
  • Implementar pruebas basadas en propiedades
  • Seguir el flujo de trabajo TDD en proyectos Kotlin
  • Configurar Kover para cobertura de código

Cómo Funciona

  1. Identificar el código objetivo — Encontrar la función, clase o módulo a probar
  2. Escribir un spec Kotest — Elegir un estilo de spec (StringSpec, FunSpec, BehaviorSpec) acorde al alcance de la prueba
  3. Mockear dependencias — Usar MockK para aislar la unidad bajo prueba
  4. Ejecutar pruebas (ROJO) — Verificar que la prueba falla con el error esperado
  5. Implementar código (VERDE) — Escribir el código mínimo para pasar la prueba
  6. Refactorizar — Mejorar la implementación manteniendo las pruebas en verde
  7. Verificar cobertura — Ejecutar ./gradlew koverHtmlReport y verificar 80%+ de cobertura

Ejemplos

Las siguientes secciones contienen ejemplos detallados y ejecutables para cada patrón de prueba:

Referencia Rápida

Flujo de Trabajo TDD para Kotlin

El Ciclo ROJO-VERDE-REFACTORIZAR

ROJO        -> Escribir primero una prueba fallida
VERDE       -> Escribir el código mínimo para pasar la prueba
REFACTORIZAR -> Mejorar el código manteniendo las pruebas en verde
REPETIR     -> Continuar con el siguiente requisito

TDD Paso a Paso en Kotlin

// Paso 1: Definir la interfaz/firma
// EmailValidator.kt
package com.example.validator

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

// Paso 2: Escribir la prueba fallida (ROJO)
// 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()
    }
})

// Paso 3: Ejecutar pruebas - verificar FALLO
// $ ./gradlew test
// EmailValidatorTest > valid email returns success FAILED
//   kotlin.NotImplementedError: An operation is not implemented

// Paso 4: Implementar el código mínimo (VERDE)
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)
}

// Paso 5: Ejecutar pruebas - verificar PASE
// $ ./gradlew test
// EmailValidatorTest > valid email returns success PASSED
// EmailValidatorTest > empty email returns failure PASSED
// EmailValidatorTest > email without @ returns failure PASSED

// Paso 6: Refactorizar si es necesario, verificar que las pruebas siguen pasando

Estilos de Spec Kotest

StringSpec (El Más Simple)

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 (Similar a JUnit)

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 (Estilo BDD)

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 (Estilo RSpec)

class UserValidatorTest : DescribeSpec({
    describe("validateUser") {
        val validator = UserValidator()

        context("with valid input") {
            it("accepts a normal user") {
                val user = CreateUserRequest("Alice", "alice@example.com")
                validator.validate(user).shouldBeValid()
            }
        }

        context("with invalid name") {
            it("rejects blank name") {
                val user = CreateUserRequest("", "alice@example.com")
                validator.validate(user).shouldBeInvalid()
            }

            it("rejects name exceeding max length") {
                val user = CreateUserRequest("A".repeat(256), "alice@example.com")
                validator.validate(user).shouldBeInvalid()
            }
        }
    }
})

Matchers de Kotest

Matchers Principales

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.*

// Igualdad
result shouldBe expected
result shouldNotBe unexpected

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

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

// Nulls
result.shouldNotBeNull()
result.shouldBeNull()

// Tipos
result.shouldBeInstanceOf<User>()

// Números
count shouldBeGreaterThan 0
price shouldBeInRange 1.0..100.0

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

shouldNotThrow<Exception> {
    validateAge(25)
}

Matchers Personalizados

fun beActiveUser() = object : Matcher<User> {
    override fun test(value: User) = MatcherResult(
        value.isActive && value.lastLogin != null,
        { "User ${value.id} should be active with a last login" },
        { "User ${value.id} should not be active" },
    )
}

// Uso
user should beActiveUser()

MockK

Mocking Básico

class UserServiceTest : FunSpec({
    val repository = mockk<UserRepository>()
    val logger = mockk<Logger>(relaxed = true) // Relaxed: retorna valores por defecto
    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()
    }
})

Mocking de Coroutines

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) // Simular trabajo asíncrono
            User(id = "1", name = "Alice")
        }

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

Captura de Argumentos

test("save captures the user argument") {
    val slot = slot<User>()
    coEvery { repository.save(capture(slot)) } returns Unit

    service.createUser(CreateUserRequest("Alice", "alice@example.com"))

    slot.captured.name shouldBe "Alice"
    slot.captured.email shouldBe "alice@example.com"
    slot.captured.id.shouldNotBeNull()
}

Spy y Mocking Parcial

test("spy on real object") {
    val realService = UserService(repository)
    val spy = spyk(realService)

    every { spy.generateId() } returns "fixed-id"

    spy.createUser(request)

    verify { spy.generateId() } // Sobreescrito
    // Otros métodos usan la implementación real
}

Pruebas de Coroutines

runTest para Funciones Suspend

import kotlinx.coroutines.test.runTest

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

            val result = service.fetchAllData()

            result.users.shouldNotBeEmpty()
            result.products.shouldNotBeEmpty()
        }
    }

    test("timeout after delay") {
        runTest {
            val service = SlowService()

            shouldThrow<TimeoutCancellationException> {
                withTimeout(100) {
                    service.slowOperation() // Tarda > 100ms
                }
            }
        }
    }
})

Pruebas de Flows

import io.kotest.matchers.collections.shouldContainInOrder
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest

class FlowServiceTest : FunSpec({
    test("observeUsers emits updates") {
        runTest {
            val service = UserFlowService()

            val emissions = service.observeUsers()
                .take(3)
                .toList()

            emissions shouldHaveSize 3
            emissions.last().shouldNotBeEmpty()
        }
    }

    test("searchUsers debounces input") {
        runTest {
            val service = SearchService()
            val queries = MutableSharedFlow<String>()

            val results = mutableListOf<List<User>>()
            val job = launch {
                service.searchUsers(queries).collect { results.add(it) }
            }

            queries.emit("a")
            queries.emit("ab")
            queries.emit("abc") // Solo este debería disparar la búsqueda
            advanceTimeBy(500)

            results shouldHaveSize 1
            job.cancel()
        }
    }
})

TestDispatcher

import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle

class DispatcherTest : FunSpec({
    test("uses test dispatcher for controlled execution") {
        val dispatcher = StandardTestDispatcher()

        runTest(dispatcher) {
            var completed = false

            launch {
                delay(1000)
                completed = true
            }

            completed shouldBe false
            advanceTimeBy(1000)
            completed shouldBe true
        }
    }
})

Pruebas Basadas en Propiedades

Pruebas de Propiedades con Kotest

import io.kotest.core.spec.style.FunSpec
import io.kotest.property.Arb
import io.kotest.property.arbitrary.*
import io.kotest.property.forAll
import io.kotest.property.checkAll
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString

// Nota: La prueba de roundtrip de serialización requiere que la data class User
// esté anotada con @Serializable (de kotlinx.serialization).

class PropertyTest : FunSpec({
    test("string reverse is involutory") {
        forAll<String> { s ->
            s.reversed().reversed() == s
        }
    }

    test("list sort is idempotent") {
        forAll(Arb.list(Arb.int())) { list ->
            list.sorted() == list.sorted().sorted()
        }
    }

    test("serialization roundtrip preserves data") {
        checkAll(Arb.bind(Arb.string(1..50), Arb.string(5..100)) { name, email ->
            User(name = name, email = "$email@test.com")
        }) { user ->
            val json = Json.encodeToString(user)
            val decoded = Json.decodeFromString<User>(json)
            decoded shouldBe user
        }
    }
})

Generadores Personalizados

val userArb: Arb<User> = Arb.bind(
    Arb.string(minSize = 1, maxSize = 50),
    Arb.email(),
    Arb.enum<Role>(),
) { name, email, role ->
    User(
        id = UserId(UUID.randomUUID().toString()),
        name = name,
        email = Email(email),
        role = role,
    )
}

val moneyArb: Arb<Money> = Arb.bind(
    Arb.long(1L..1_000_000L),
    Arb.enum<Currency>(),
) { amount, currency ->
    Money(amount, currency)
}

Pruebas Dirigidas por Datos

withData en Kotest

class ParserTest : FunSpec({
    context("parsing valid dates") {
        withData(
            "2026-01-15" to LocalDate(2026, 1, 15),
            "2026-12-31" to LocalDate(2026, 12, 31),
            "2000-01-01" to LocalDate(2000, 1, 1),
        ) { (input, expected) ->
            parseDate(input) shouldBe expected
        }
    }

    context("rejecting invalid dates") {
        withData(
            nameFn = { "rejects '$it'" },
            "not-a-date",
            "2026-13-01",
            "2026-00-15",
            "",
        ) { input ->
            shouldThrow<DateParseException> {
                parseDate(input)
            }
        }
    }
})

Ciclo de Vida y Fixtures de Prueba

BeforeTest / AfterTest

class DatabaseTest : FunSpec({
    lateinit var db: Database

    beforeSpec {
        db = Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1")
        transaction(db) {
            SchemaUtils.create(UsersTable)
        }
    }

    afterSpec {
        transaction(db) {
            SchemaUtils.drop(UsersTable)
        }
    }

    beforeTest {
        transaction(db) {
            UsersTable.deleteAll()
        }
    }

    test("insert and retrieve user") {
        transaction(db) {
            UsersTable.insert {
                it[name] = "Alice"
                it[email] = "alice@example.com"
            }
        }

        val users = transaction(db) {
            UsersTable.selectAll().map { it[UsersTable.name] }
        }

        users shouldContain "Alice"
    }
})

Extensiones de Kotest

// Extensión de prueba reutilizable
class DatabaseExtension : BeforeSpecListener, AfterSpecListener {
    lateinit var db: Database

    override suspend fun beforeSpec(spec: Spec) {
        db = Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1")
    }

    override suspend fun afterSpec(spec: Spec) {
        // limpieza
    }
}

class UserRepositoryTest : FunSpec({
    val dbExt = DatabaseExtension()
    register(dbExt)

    test("save and find user") {
        val repo = UserRepository(dbExt.db)
        // ...
    }
})

Cobertura con Kover

Configuración de Gradle

// build.gradle.kts
plugins {
    id("org.jetbrains.kotlinx.kover") version "0.9.7"
}

kover {
    reports {
        total {
            html { onCheck = true }
            xml { onCheck = true }
        }
        filters {
            excludes {
                classes("*.generated.*", "*.config.*")
            }
        }
        verify {
            rule {
                minBound(80) // Fallar el build por debajo del 80% de cobertura
            }
        }
    }
}

Comandos de Cobertura

# Ejecutar pruebas con cobertura
./gradlew koverHtmlReport

# Verificar umbrales de cobertura
./gradlew koverVerify

# Reporte XML para CI
./gradlew koverXmlReport

# Ver reporte HTML (usa el comando para tu SO)
# macOS:   open build/reports/kover/html/index.html
# Linux:   xdg-open build/reports/kover/html/index.html
# Windows: start build/reports/kover/html/index.html

Objetivos de Cobertura

Tipo de CódigoObjetivo
Lógica de negocio crítica100%
APIs públicas90%+
Código general80%+
Código generado / configuraciónExcluir

Pruebas con Ktor testApplication

class ApiRoutesTest : FunSpec({
    test("GET /users returns list") {
        testApplication {
            application {
                configureRouting()
                configureSerialization()
            }

            val response = client.get("/users")

            response.status shouldBe HttpStatusCode.OK
            val users = response.body<List<UserResponse>>()
            users.shouldNotBeEmpty()
        }
    }

    test("POST /users creates user") {
        testApplication {
            application {
                configureRouting()
                configureSerialization()
            }

            val response = client.post("/users") {
                contentType(ContentType.Application.Json)
                setBody(CreateUserRequest("Alice", "alice@example.com"))
            }

            response.status shouldBe HttpStatusCode.Created
        }
    }
})

Comandos de Prueba

# Ejecutar todas las pruebas
./gradlew test

# Ejecutar clase de prueba específica
./gradlew test --tests "com.example.UserServiceTest"

# Ejecutar prueba específica
./gradlew test --tests "com.example.UserServiceTest.getUser returns user when found"

# Ejecutar con salida detallada
./gradlew test --info

# Ejecutar con cobertura
./gradlew koverHtmlReport

# Ejecutar detekt (análisis estático)
./gradlew detekt

# Ejecutar ktlint (verificación de formato)
./gradlew ktlintCheck

# Pruebas continuas
./gradlew test --continuous

Buenas Prácticas

HACER:

  • Escribir pruebas PRIMERO (TDD)
  • Usar los estilos de spec de Kotest de forma consistente en el proyecto
  • Usar coEvery/coVerify de MockK para funciones suspend
  • Usar runTest para pruebas de coroutines
  • Probar comportamiento, no implementación
  • Usar pruebas basadas en propiedades para funciones puras
  • Usar fixtures de data class para mayor claridad

NO HACER:

  • Mezclar frameworks de prueba (elegir Kotest y mantenerlo)
  • Mockear data classes (usar instancias reales)
  • Usar Thread.sleep() en pruebas de coroutines (usar advanceTimeBy)
  • Saltarse la fase ROJA en TDD
  • Probar funciones privadas directamente
  • Ignorar pruebas inestables (flaky tests)

Integración con CI/CD

# Ejemplo de GitHub Actions
test:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-java@v4
      with:
        distribution: 'temurin'
        java-version: '21'

    - name: Run tests with coverage
      run: ./gradlew test koverXmlReport

    - name: Verify coverage
      run: ./gradlew koverVerify

    - name: Upload coverage
      uses: codecov/codecov-action@v5
      with:
        files: build/reports/kover/report.xml
        token: ${{ secrets.CODECOV_TOKEN }}

Recuerda: Las pruebas son documentación. Muestran cómo debe usarse tu código Kotlin. Usa los matchers expresivos de Kotest para que las pruebas sean legibles y MockK para un mocking limpio de dependencias.

Source repo
affaan-m/ECC
Skill path
docs/es/skills/kotlin-testing/SKILL.md
Commit SHA
4e973d3eaf92
Repository license
MIT
Data collected