Android Testing
rcosteira79/android-skills
A skill your agent uses when writing, fixing, or refactoring Android/KMP code in Kotlin — a test-first (RED-GREEN-REFACTOR) foundation plus the Android/KMP test traps: Compose-test dispatching…
Patrones de pruebas Kotlin con Kotest, MockK, pruebas de coroutines, pruebas basadas en propiedades y cobertura con Kover.
$ npx skills add affaan-m/ECC --skill kotlin-testing -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install affaan-m/ECC kotlin-testing --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/affaan-m/ECC.git skills-src && mkdir -p .claude/skills && cp -r skills-src/docs/es/skills/kotlin-testing .claude/skills/kotlin-testing && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "kotlin-testing" agent skill from https://github.com/affaan-m/ECC/tree/main/docs/es/skills/kotlin-testing into .claude/skills/kotlin-testing/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kotlin-testing", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/affaan-m/ECC/tree/main/docs/es/skills/kotlin-testingType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add affaan-m/ECC --skill kotlin-testing -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install affaan-m/ECC kotlin-testing --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/affaan-m/ECC.git skills-src && mkdir -p .agents/skills && cp -r skills-src/docs/es/skills/kotlin-testing .agents/skills/kotlin-testing && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "kotlin-testing" agent skill from https://github.com/affaan-m/ECC/tree/main/docs/es/skills/kotlin-testing into .agents/skills/kotlin-testing/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kotlin-testing", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add affaan-m/ECC --skill kotlin-testing -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install affaan-m/ECC kotlin-testing --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/affaan-m/ECC.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/docs/es/skills/kotlin-testing .cursor/skills/kotlin-testing && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "kotlin-testing" agent skill from https://github.com/affaan-m/ECC/tree/main/docs/es/skills/kotlin-testing into .cursor/skills/kotlin-testing/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kotlin-testing", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/affaan-m/ECC.git --path docs/es/skills/kotlin-testing--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add affaan-m/ECC --skill kotlin-testing -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install affaan-m/ECC kotlin-testing --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/affaan-m/ECC.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/docs/es/skills/kotlin-testing .gemini/skills/kotlin-testing && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "kotlin-testing" agent skill from https://github.com/affaan-m/ECC/tree/main/docs/es/skills/kotlin-testing into .gemini/skills/kotlin-testing/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kotlin-testing", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install affaan-m/ECC kotlin-testingInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add affaan-m/ECC --skill kotlin-testing -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/affaan-m/ECC.git skills-src && mkdir -p .github/skills && cp -r skills-src/docs/es/skills/kotlin-testing .github/skills/kotlin-testing && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "kotlin-testing" agent skill from https://github.com/affaan-m/ECC/tree/main/docs/es/skills/kotlin-testing into .github/skills/kotlin-testing/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kotlin-testing", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add affaan-m/ECC --skill kotlin-testing -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install affaan-m/ECC kotlin-testing --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/affaan-m/ECC.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/docs/es/skills/kotlin-testing .opencode/skills/kotlin-testing && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "kotlin-testing" agent skill from https://github.com/affaan-m/ECC/tree/main/docs/es/skills/kotlin-testing into .opencode/skills/kotlin-testing/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "kotlin-testing", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
kotlin-testingPatrones de pruebas Kotlin con Kotest, MockK, pruebas de coroutines, pruebas basadas en propiedades y cobertura con Kover.
Kotlin Testing is an agent skill from affaan-m/ECC. 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.
Its SKILL.md is about 5.4k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Mobile, covering Android development and Test-driven development. It works with Kotlin. The repository describes itself as: The agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond. The licence is MIT.
7 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 4eb71d9. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are kotlin, bash and yaml).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
CODECOV_TOKENFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Kotlin Testing loads about 5.4k tokens when it runs. Until then it costs about 50 tokens; SKILL.md has 486 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from affaan-m/ECC at commit 4eb71d9, republished under its MIT licence (© affaan-m). 486 words, ~5,365 tokens.
.claude/skills/kotlin-testing/SKILL.md (or your agent's skills folder).Patrones completos de pruebas Kotlin para escribir pruebas confiables y mantenibles siguiendo la metodología TDD con Kotest y MockK.
./gradlew koverHtmlReport y verificar 80%+ de coberturaLas siguientes secciones contienen ejemplos detallados y ejecutables para cada patrón de prueba:
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// 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 pasandoclass 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
}
})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")
}
}
})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)
}
}
}
}
})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()
}
}
}
})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)
}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()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()
}
})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"
}
})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()
}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
}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
}
}
}
}
})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()
}
}
})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
}
}
})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
}
}
})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)
}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)
}
}
}
})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"
}
})// 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)
// ...
}
})// 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
}
}
}
}# 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| Tipo de Código | Objetivo |
|---|---|
| Lógica de negocio crítica | 100% |
| APIs públicas | 90%+ |
| Código general | 80%+ |
| Código generado / configuración | Excluir |
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
}
}
})# 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 --continuousHACER:
coEvery/coVerify de MockK para funciones suspendrunTest para pruebas de coroutinesdata class para mayor claridadNO HACER:
Thread.sleep() en pruebas de coroutines (usar advanceTimeBy)# 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.
© affaan-m, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in docs/es/skills/kotlin-testing of affaan-m/ECC.
Open the folder on GitHubat commit 4eb71d9
Kotlin Testing next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Kotlin Testing this skillaffaan-m/ECC | 276k | — | ~5.4k | Automated safety check: Pass | MIT | |
| Android Testingrcosteira79/android-skills | 153 | — | ~2.1k | Automated safety check: Pass | MIT | |
| Compose Multiplatform Patternsmonta-app/ocpp-emulator | 180 | 5 repos | ~2k | Automated safety check: Pass | Apache-2.0 | |
| Kotlin Patternsgetknit/knit | 133 | 5 repos | ~4.7k | Automated safety check: Pass | GPL-3.0 | |
| Summoncodeyousef/summon | 167 | — | ~1.1k | Automated safety check: Pass | Apache-2.0 | |
| Android Audio E2Ehyochan/react-native-nitro-sound | 961 | — | ~814 | Automated safety check: Pass | MIT |
rcosteira79/android-skills
A skill your agent uses when writing, fixing, or refactoring Android/KMP code in Kotlin — a test-first (RED-GREEN-REFACTOR) foundation plus the Android/KMP test traps: Compose-test dispatching…
monta-app/ocpp-emulator
Compose Multiplatform and Jetpack Compose patterns for KMP projects — state management, navigation, theming, performance, and platform-specific UI.
getknit/knit
Idiomatic Kotlin patterns, best practices, and conventions for building robust, efficient, and maintainable Kotlin applications with coroutines, null safety, and DSL builders.
codeyousef/summon
A skill your agent uses when developing applications with the Summon Kotlin Multiplatform UI framework.
hyochan/react-native-nitro-sound
Build and run device-backed Android end-to-end checks for react-native-nitro-sound permissions, recording, playback, listeners, pause/resume, seek, speed, audio focus, lifecycle behavior, and rapid…
alexvanyo/composelife
Helps increment code coverage in this Kotlin Multiplatform project.
affaan-m/ECC
Audits your installed Claude skills and commands for quality, with a quick mode for recently changed skills and a full mode that evaluates all of them through subagents.
affaan-m/ECC
Ingests, indexes, searches, edits and monitors video, audio and live streams through the VideoDB Python SDK, returning stream links, clips and timestamps.
affaan-m/ECC
Route broad documentation-governance requests to existing ECC skills and run an opt-in, read-only audit of mapped documentation roles, links, ADR indexes, and evidence references.
affaan-m/ECC
Scans installed skills for principles that recur across them and proposes rule-file changes: append, revise, add a section, create a file or leave as covered.
affaan-m/ECC
Builds DRAFT counterparty agreements from one markdown template and a small JSON spec per party, with clauses picked by the party's role.
affaan-m/ECC
Measures whether agents actually follow a skill, rule or agent definition by generating scenarios at three strictness levels and scoring tool-call traces.
Works with
Categories
Patrones de pruebas Kotlin con Kotest, MockK, pruebas de coroutines, pruebas basadas en propiedades y cobertura con Kover. Kotlin Testing is an agent skill from affaan-m/ECC. Patrones de pruebas Kotlin con Kotest, MockK, pruebas de coroutines, pruebas basadas en propiedades y cobertura con Kover.
Kotlin Testing fits situations like: tasks that involve Android development; tasks that involve Test-driven development.
Run `npx skills add affaan-m/ECC --skill kotlin-testing -a claude-code`. Or copy the skill folder (docs/es/skills/kotlin-testing in affaan-m/ECC) into .claude/skills/kotlin-testing in your project. Claude Code loads it when a task matches its description.
Run `npx skills add affaan-m/ECC --skill kotlin-testing -a codex`. Or copy the skill folder (docs/es/skills/kotlin-testing in affaan-m/ECC) into .agents/skills/kotlin-testing in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add affaan-m/ECC --skill kotlin-testing -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/kotlin-testing, .gemini/skills/kotlin-testing, .github/skills/kotlin-testing and .opencode/skills/kotlin-testing in your project.
Going by SKILL.md and its folder, Kotlin Testing needs credentials named CODECOV_TOKEN.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Kotlin Testing is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.4k tokens (SKILL.md is roughly 21k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Kotlin Testing: Android Testing (rcosteira79/android-skills, 153 stars), Compose Multiplatform Patterns (monta-app/ocpp-emulator, 180 stars), Kotlin Patterns (getknit/knit, 133 stars) and Summon (codeyousef/summon, 167 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
affaan-m (a GitHub user) maintains it in affaan-m/ECC, which has 276,111 GitHub stars. The repository holds 683 skills in this directory. The repository was last updated on October 10, 2026.
Source: affaan-m/ECC on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.