[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M73DF0B0V9D0T898FHSFFHT6":3,"skill-same:01M73DF0B0V9D0T898FHSFFHT6":49},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":25,"commitSha":26,"skillDirectory":27,"forksCount":28,"repositoryHtmlUrl":29,"repositoryHomepageUrl":30,"ownerHtmlUrl":31,"ownerBlogUrl":32,"ownerDescription":33,"updatedAt":34,"install":35,"resources":48},"01M73DF0B0V9D0T898FHSFFHT6","kotlin-testing",null,"Kotlin testing patterns with Kotest, MockK, coroutine testing, property-based testing, and Kover coverage. Follows TDD methodology with idiomatic Kotlin practices.","Horonomos-laps\u002FYana-AI","Horonomos-laps","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F355299122?v=4","01M76B3QPXE88ET2SQPDPVATZT"," Horonomos-laps","01M72ZQRJPCYPSSWT4D00VJR2A",13,"Apache-2.0","https:\u002F\u002Fgithub.com\u002FHoronomos-laps\u002FYana-AI\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fkotlin-testing\u002FSKILL.md",[],[],"2026-11-11 15:11:14",false,"---\nname: kotlin-testing\ndescription: Kotlin testing patterns with Kotest, MockK, coroutine testing, property-based testing, and Kover coverage. Follows TDD methodology with idiomatic Kotlin practices.\norigin: ECC\n---\n\n# Kotlin Testing Patterns\n\nComprehensive Kotlin testing patterns for writing reliable, maintainable tests following TDD methodology with Kotest and MockK.\n\n## When to Use\n\n- Writing new Kotlin functions or classes\n- Adding test coverage to existing Kotlin code\n- Implementing property-based tests\n- Following TDD workflow in Kotlin projects\n- Configuring Kover for code coverage\n\n## How It Works\n\n1. **Identify target code** — Find the function, class, or module to test\n2. **Write a Kotest spec** — Choose a spec style (StringSpec, FunSpec, BehaviorSpec) matching the test scope\n3. **Mock dependencies** — Use MockK to isolate the unit under test\n4. **Run tests (RED)** — Verify the test fails with the expected error\n5. **Implement code (GREEN)** — Write minimal code to pass the test\n6. **Refactor** — Improve the implementation while keeping tests green\n7. **Check coverage** — Run `.\u002Fgradlew koverHtmlReport` and verify 80%+ coverage\n\n## Examples\n\nThe following sections contain detailed, runnable examples for each testing pattern:\n\n### Quick Reference\n\n- **Kotest specs** — StringSpec, FunSpec, BehaviorSpec, DescribeSpec examples in [Kotest Spec Styles](#kotest-spec-styles)\n- **Mocking** — MockK setup, coroutine mocking, argument capture in [MockK](#mockk)\n- **TDD walkthrough** — Full RED\u002FGREEN\u002FREFACTOR cycle with EmailValidator in [TDD Workflow for Kotlin](#tdd-workflow-for-kotlin)\n- **Coverage** — Kover configuration and commands in [Kover Coverage](#kover-coverage)\n- **Ktor testing** — testApplication setup in [Ktor testApplication Testing](#ktor-testapplication-testing)\n\n### TDD Workflow for Kotlin\n\n#### The RED-GREEN-REFACTOR Cycle\n\n```\nRED     -> Write a failing test first\nGREEN   -> Write minimal code to pass the test\nREFACTOR -> Improve code while keeping tests green\nREPEAT  -> Continue with next requirement\n```\n\n#### Step-by-Step TDD in Kotlin\n\n```kotlin\n\u002F\u002F Step 1: Define the interface\u002Fsignature\n\u002F\u002F EmailValidator.kt\npackage com.example.validator\n\nfun validateEmail(email: String): Result\u003CString> {\n    TODO(\"not implemented\")\n}\n\n\u002F\u002F Step 2: Write failing test (RED)\n\u002F\u002F EmailValidatorTest.kt\npackage com.example.validator\n\nimport io.kotest.core.spec.style.StringSpec\nimport io.kotest.matchers.result.shouldBeFailure\nimport io.kotest.matchers.result.shouldBeSuccess\n\nclass EmailValidatorTest : StringSpec({\n    \"valid email returns success\" {\n        validateEmail(\"user@example.com\").shouldBeSuccess(\"user@example.com\")\n    }\n\n    \"empty email returns failure\" {\n        validateEmail(\"\").shouldBeFailure()\n    }\n\n    \"email without @ returns failure\" {\n        validateEmail(\"userexample.com\").shouldBeFailure()\n    }\n})\n\n\u002F\u002F Step 3: Run tests - verify FAIL\n\u002F\u002F $ .\u002Fgradlew test\n\u002F\u002F EmailValidatorTest > valid email returns success FAILED\n\u002F\u002F   kotlin.NotImplementedError: An operation is not implemented\n\n\u002F\u002F Step 4: Implement minimal code (GREEN)\nfun validateEmail(email: String): Result\u003CString> {\n    if (email.isBlank()) return Result.failure(IllegalArgumentException(\"Email cannot be blank\"))\n    if ('@' !in email) return Result.failure(IllegalArgumentException(\"Email must contain @\"))\n    val regex = Regex(\"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Za-z]{2,}$\")\n    if (!regex.matches(email)) return Result.failure(IllegalArgumentException(\"Invalid email format\"))\n    return Result.success(email)\n}\n\n\u002F\u002F Step 5: Run tests - verify PASS\n\u002F\u002F $ .\u002Fgradlew test\n\u002F\u002F EmailValidatorTest > valid email returns success PASSED\n\u002F\u002F EmailValidatorTest > empty email returns failure PASSED\n\u002F\u002F EmailValidatorTest > email without @ returns failure PASSED\n\n\u002F\u002F Step 6: Refactor if needed, verify tests still pass\n```\n\n### Kotest Spec Styles\n\n#### StringSpec (Simplest)\n\n```kotlin\nclass CalculatorTest : StringSpec({\n    \"add two positive numbers\" {\n        Calculator.add(2, 3) shouldBe 5\n    }\n\n    \"add negative numbers\" {\n        Calculator.add(-1, -2) shouldBe -3\n    }\n\n    \"add zero\" {\n        Calculator.add(0, 5) shouldBe 5\n    }\n})\n```\n\n#### FunSpec (JUnit-like)\n\n```kotlin\nclass UserServiceTest : FunSpec({\n    val repository = mockk\u003CUserRepository>()\n    val service = UserService(repository)\n\n    test(\"getUser returns user when found\") {\n        val expected = User(id = \"1\", name = \"Alice\")\n        coEvery { repository.findById(\"1\") } returns expected\n\n        val result = service.getUser(\"1\")\n\n        result shouldBe expected\n    }\n\n    test(\"getUser throws when not found\") {\n        coEvery { repository.findById(\"999\") } returns null\n\n        shouldThrow\u003CUserNotFoundException> {\n            service.getUser(\"999\")\n        }\n    }\n})\n```\n\n#### BehaviorSpec (BDD Style)\n\n```kotlin\nclass OrderServiceTest : BehaviorSpec({\n    val repository = mockk\u003COrderRepository>()\n    val paymentService = mockk\u003CPaymentService>()\n    val service = OrderService(repository, paymentService)\n\n    Given(\"a valid order request\") {\n        val request = CreateOrderRequest(\n            userId = \"user-1\",\n            items = listOf(OrderItem(\"product-1\", quantity = 2)),\n        )\n\n        When(\"the order is placed\") {\n            coEvery { paymentService.charge(any()) } returns PaymentResult.Success\n            coEvery { repository.save(any()) } answers { firstArg() }\n\n            val result = service.placeOrder(request)\n\n            Then(\"it should return a confirmed order\") {\n                result.status shouldBe OrderStatus.CONFIRMED\n            }\n\n            Then(\"it should charge payment\") {\n                coVerify(exactly = 1) { paymentService.charge(any()) }\n            }\n        }\n\n        When(\"payment fails\") {\n            coEvery { paymentService.charge(any()) } returns PaymentResult.Declined\n\n            Then(\"it should throw PaymentException\") {\n                shouldThrow\u003CPaymentException> {\n                    service.placeOrder(request)\n                }\n            }\n        }\n    }\n})\n```\n\n#### DescribeSpec (RSpec Style)\n\n```kotlin\nclass UserValidatorTest : DescribeSpec({\n    describe(\"validateUser\") {\n        val validator = UserValidator()\n\n        context(\"with valid input\") {\n            it(\"accepts a normal user\") {\n                val user = CreateUserRequest(\"Alice\", \"alice@example.com\")\n                validator.validate(user).shouldBeValid()\n            }\n        }\n\n        context(\"with invalid name\") {\n            it(\"rejects blank name\") {\n                val user = CreateUserRequest(\"\", \"alice@example.com\")\n                validator.validate(user).shouldBeInvalid()\n            }\n\n            it(\"rejects name exceeding max length\") {\n                val user = CreateUserRequest(\"A\".repeat(256), \"alice@example.com\")\n                validator.validate(user).shouldBeInvalid()\n            }\n        }\n    }\n})\n```\n\n### Kotest Matchers\n\n#### Core Matchers\n\n```kotlin\nimport io.kotest.matchers.shouldBe\nimport io.kotest.matchers.shouldNotBe\nimport io.kotest.matchers.string.*\nimport io.kotest.matchers.collections.*\nimport io.kotest.matchers.nulls.*\n\n\u002F\u002F Equality\nresult shouldBe expected\nresult shouldNotBe unexpected\n\n\u002F\u002F Strings\nname shouldStartWith \"Al\"\nname shouldEndWith \"ice\"\nname shouldContain \"lic\"\nname shouldMatch Regex(\"[A-Z][a-z]+\")\nname.shouldBeBlank()\n\n\u002F\u002F Collections\nlist shouldContain \"item\"\nlist shouldHaveSize 3\nlist.shouldBeSorted()\nlist.shouldContainAll(\"a\", \"b\", \"c\")\nlist.shouldBeEmpty()\n\n\u002F\u002F Nulls\nresult.shouldNotBeNull()\nresult.shouldBeNull()\n\n\u002F\u002F Types\nresult.shouldBeInstanceOf\u003CUser>()\n\n\u002F\u002F Numbers\ncount shouldBeGreaterThan 0\nprice shouldBeInRange 1.0..100.0\n\n\u002F\u002F Exceptions\nshouldThrow\u003CIllegalArgumentException> {\n    validateAge(-1)\n}.message shouldBe \"Age must be positive\"\n\nshouldNotThrow\u003CException> {\n    validateAge(25)\n}\n```\n\n#### Custom Matchers\n\n```kotlin\nfun beActiveUser() = object : Matcher\u003CUser> {\n    override fun test(value: User) = MatcherResult(\n        value.isActive && value.lastLogin != null,\n        { \"User ${value.id} should be active with a last login\" },\n        { \"User ${value.id} should not be active\" },\n    )\n}\n\n\u002F\u002F Usage\nuser should beActiveUser()\n```\n\n### MockK\n\n#### Basic Mocking\n\n```kotlin\nclass UserServiceTest : FunSpec({\n    val repository = mockk\u003CUserRepository>()\n    val logger = mockk\u003CLogger>(relaxed = true) \u002F\u002F Relaxed: returns defaults\n    val service = UserService(repository, logger)\n\n    beforeTest {\n        clearMocks(repository, logger)\n    }\n\n    test(\"findUser delegates to repository\") {\n        val expected = User(id = \"1\", name = \"Alice\")\n        every { repository.findById(\"1\") } returns expected\n\n        val result = service.findUser(\"1\")\n\n        result shouldBe expected\n        verify(exactly = 1) { repository.findById(\"1\") }\n    }\n\n    test(\"findUser returns null for unknown id\") {\n        every { repository.findById(any()) } returns null\n\n        val result = service.findUser(\"unknown\")\n\n        result.shouldBeNull()\n    }\n})\n```\n\n#### Coroutine Mocking\n\n```kotlin\nclass AsyncUserServiceTest : FunSpec({\n    val repository = mockk\u003CUserRepository>()\n    val service = UserService(repository)\n\n    test(\"getUser suspending function\") {\n        coEvery { repository.findById(\"1\") } returns User(id = \"1\", name = \"Alice\")\n\n        val result = service.getUser(\"1\")\n\n        result.name shouldBe \"Alice\"\n        coVerify { repository.findById(\"1\") }\n    }\n\n    test(\"getUser with delay\") {\n        coEvery { repository.findById(\"1\") } coAnswers {\n            delay(100) \u002F\u002F Simulate async work\n            User(id = \"1\", name = \"Alice\")\n        }\n\n        val result = service.getUser(\"1\")\n        result.name shouldBe \"Alice\"\n    }\n})\n```\n\n#### Argument Capture\n\n```kotlin\ntest(\"save captures the user argument\") {\n    val slot = slot\u003CUser>()\n    coEvery { repository.save(capture(slot)) } returns Unit\n\n    service.createUser(CreateUserRequest(\"Alice\", \"alice@example.com\"))\n\n    slot.captured.name shouldBe \"Alice\"\n    slot.captured.email shouldBe \"alice@example.com\"\n    slot.captured.id.shouldNotBeNull()\n}\n```\n\n#### Spy and Partial Mocking\n\n```kotlin\ntest(\"spy on real object\") {\n    val realService = UserService(repository)\n    val spy = spyk(realService)\n\n    every { spy.generateId() } returns \"fixed-id\"\n\n    spy.createUser(request)\n\n    verify { spy.generateId() } \u002F\u002F Overridden\n    \u002F\u002F Other methods use real implementation\n}\n```\n\n### Coroutine Testing\n\n#### runTest for Suspend Functions\n\n```kotlin\nimport kotlinx.coroutines.test.runTest\n\nclass CoroutineServiceTest : FunSpec({\n    test(\"concurrent fetches complete together\") {\n        runTest {\n            val service = DataService(testScope = this)\n\n            val result = service.fetchAllData()\n\n            result.users.shouldNotBeEmpty()\n            result.products.shouldNotBeEmpty()\n        }\n    }\n\n    test(\"timeout after delay\") {\n        runTest {\n            val service = SlowService()\n\n            shouldThrow\u003CTimeoutCancellationException> {\n                withTimeout(100) {\n                    service.slowOperation() \u002F\u002F Takes > 100ms\n                }\n            }\n        }\n    }\n})\n```\n\n#### Testing Flows\n\n```kotlin\nimport io.kotest.matchers.collections.shouldContainInOrder\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.advanceTimeBy\nimport kotlinx.coroutines.test.runTest\n\nclass FlowServiceTest : FunSpec({\n    test(\"observeUsers emits updates\") {\n        runTest {\n            val service = UserFlowService()\n\n            val emissions = service.observeUsers()\n                .take(3)\n                .toList()\n\n            emissions shouldHaveSize 3\n            emissions.last().shouldNotBeEmpty()\n        }\n    }\n\n    test(\"searchUsers debounces input\") {\n        runTest {\n            val service = SearchService()\n            val queries = MutableSharedFlow\u003CString>()\n\n            val results = mutableListOf\u003CList\u003CUser>>()\n            val job = launch {\n                service.searchUsers(queries).collect { results.add(it) }\n            }\n\n            queries.emit(\"a\")\n            queries.emit(\"ab\")\n            queries.emit(\"abc\") \u002F\u002F Only this should trigger search\n            advanceTimeBy(500)\n\n            results shouldHaveSize 1\n            job.cancel()\n        }\n    }\n})\n```\n\n#### TestDispatcher\n\n```kotlin\nimport kotlinx.coroutines.test.StandardTestDispatcher\nimport kotlinx.coroutines.test.advanceUntilIdle\n\nclass DispatcherTest : FunSpec({\n    test(\"uses test dispatcher for controlled execution\") {\n        val dispatcher = StandardTestDispatcher()\n\n        runTest(dispatcher) {\n            var completed = false\n\n            launch {\n                delay(1000)\n                completed = true\n            }\n\n            completed shouldBe false\n            advanceTimeBy(1000)\n            completed shouldBe true\n        }\n    }\n})\n```\n\n### Property-Based Testing\n\n#### Kotest Property Testing\n\n```kotlin\nimport io.kotest.core.spec.style.FunSpec\nimport io.kotest.property.Arb\nimport io.kotest.property.arbitrary.*\nimport io.kotest.property.forAll\nimport io.kotest.property.checkAll\nimport kotlinx.serialization.json.Json\nimport kotlinx.serialization.encodeToString\nimport kotlinx.serialization.decodeFromString\n\n\u002F\u002F Note: The serialization roundtrip test below requires the User data class\n\u002F\u002F to be annotated with @Serializable (from kotlinx.serialization).\n\nclass PropertyTest : FunSpec({\n    test(\"string reverse is involutory\") {\n        forAll\u003CString> { s ->\n            s.reversed().reversed() == s\n        }\n    }\n\n    test(\"list sort is idempotent\") {\n        forAll(Arb.list(Arb.int())) { list ->\n            list.sorted() == list.sorted().sorted()\n        }\n    }\n\n    test(\"serialization roundtrip preserves data\") {\n        checkAll(Arb.bind(Arb.string(1..50), Arb.string(5..100)) { name, email ->\n            User(name = name, email = \"$email@test.com\")\n        }) { user ->\n            val json = Json.encodeToString(user)\n            val decoded = Json.decodeFromString\u003CUser>(json)\n            decoded shouldBe user\n        }\n    }\n})\n```\n\n#### Custom Generators\n\n```kotlin\nval userArb: Arb\u003CUser> = Arb.bind(\n    Arb.string(minSize = 1, maxSize = 50),\n    Arb.email(),\n    Arb.enum\u003CRole>(),\n) { name, email, role ->\n    User(\n        id = UserId(UUID.randomUUID().toString()),\n        name = name,\n        email = Email(email),\n        role = role,\n    )\n}\n\nval moneyArb: Arb\u003CMoney> = Arb.bind(\n    Arb.long(1L..1_000_000L),\n    Arb.enum\u003CCurrency>(),\n) { amount, currency ->\n    Money(amount, currency)\n}\n```\n\n### Data-Driven Testing\n\n#### withData in Kotest\n\n```kotlin\nclass ParserTest : FunSpec({\n    context(\"parsing valid dates\") {\n        withData(\n            \"2026-01-15\" to LocalDate(2026, 1, 15),\n            \"2026-12-31\" to LocalDate(2026, 12, 31),\n            \"2000-01-01\" to LocalDate(2000, 1, 1),\n        ) { (input, expected) ->\n            parseDate(input) shouldBe expected\n        }\n    }\n\n    context(\"rejecting invalid dates\") {\n        withData(\n            nameFn = { \"rejects '$it'\" },\n            \"not-a-date\",\n            \"2026-13-01\",\n            \"2026-00-15\",\n            \"\",\n        ) { input ->\n            shouldThrow\u003CDateParseException> {\n                parseDate(input)\n            }\n        }\n    }\n})\n```\n\n### Test Lifecycle and Fixtures\n\n#### BeforeTest \u002F AfterTest\n\n```kotlin\nclass DatabaseTest : FunSpec({\n    lateinit var db: Database\n\n    beforeSpec {\n        db = Database.connect(\"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1\")\n        transaction(db) {\n            SchemaUtils.create(UsersTable)\n        }\n    }\n\n    afterSpec {\n        transaction(db) {\n            SchemaUtils.drop(UsersTable)\n        }\n    }\n\n    beforeTest {\n        transaction(db) {\n            UsersTable.deleteAll()\n        }\n    }\n\n    test(\"insert and retrieve user\") {\n        transaction(db) {\n            UsersTable.insert {\n                it[name] = \"Alice\"\n                it[email] = \"alice@example.com\"\n            }\n        }\n\n        val users = transaction(db) {\n            UsersTable.selectAll().map { it[UsersTable.name] }\n        }\n\n        users shouldContain \"Alice\"\n    }\n})\n```\n\n#### Kotest Extensions\n\n```kotlin\n\u002F\u002F Reusable test extension\nclass DatabaseExtension : BeforeSpecListener, AfterSpecListener {\n    lateinit var db: Database\n\n    override suspend fun beforeSpec(spec: Spec) {\n        db = Database.connect(\"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1\")\n    }\n\n    override suspend fun afterSpec(spec: Spec) {\n        \u002F\u002F cleanup\n    }\n}\n\nclass UserRepositoryTest : FunSpec({\n    val dbExt = DatabaseExtension()\n    register(dbExt)\n\n    test(\"save and find user\") {\n        val repo = UserRepository(dbExt.db)\n        \u002F\u002F ...\n    }\n})\n```\n\n### Kover Coverage\n\n#### Gradle Configuration\n\n```kotlin\n\u002F\u002F build.gradle.kts\nplugins {\n    id(\"org.jetbrains.kotlinx.kover\") version \"0.9.7\"\n}\n\nkover {\n    reports {\n        total {\n            html { onCheck = true }\n            xml { onCheck = true }\n        }\n        filters {\n            excludes {\n                classes(\"*.generated.*\", \"*.config.*\")\n            }\n        }\n        verify {\n            rule {\n                minBound(80) \u002F\u002F Fail build below 80% coverage\n            }\n        }\n    }\n}\n```\n\n#### Coverage Commands\n\n```bash\n# Run tests with coverage\n.\u002Fgradlew koverHtmlReport\n\n# Verify coverage thresholds\n.\u002Fgradlew koverVerify\n\n# XML report for CI\n.\u002Fgradlew koverXmlReport\n\n# View HTML report (use the command for your OS)\n# macOS:   open build\u002Freports\u002Fkover\u002Fhtml\u002Findex.html\n# Linux:   xdg-open build\u002Freports\u002Fkover\u002Fhtml\u002Findex.html\n# Windows: start build\u002Freports\u002Fkover\u002Fhtml\u002Findex.html\n```\n\n#### Coverage Targets\n\n| Code Type | Target |\n|-----------|--------|\n| Critical business logic | 100% |\n| Public APIs | 90%+ |\n| General code | 80%+ |\n| Generated \u002F config code | Exclude |\n\n### Ktor testApplication Testing\n\n```kotlin\nclass ApiRoutesTest : FunSpec({\n    test(\"GET \u002Fusers returns list\") {\n        testApplication {\n            application {\n                configureRouting()\n                configureSerialization()\n            }\n\n            val response = client.get(\"\u002Fusers\")\n\n            response.status shouldBe HttpStatusCode.OK\n            val users = response.body\u003CList\u003CUserResponse>>()\n            users.shouldNotBeEmpty()\n        }\n    }\n\n    test(\"POST \u002Fusers creates user\") {\n        testApplication {\n            application {\n                configureRouting()\n                configureSerialization()\n            }\n\n            val response = client.post(\"\u002Fusers\") {\n                contentType(ContentType.Application.Json)\n                setBody(CreateUserRequest(\"Alice\", \"alice@example.com\"))\n            }\n\n            response.status shouldBe HttpStatusCode.Created\n        }\n    }\n})\n```\n\n### Testing Commands\n\n```bash\n# Run all tests\n.\u002Fgradlew test\n\n# Run specific test class\n.\u002Fgradlew test --tests \"com.example.UserServiceTest\"\n\n# Run specific test\n.\u002Fgradlew test --tests \"com.example.UserServiceTest.getUser returns user when found\"\n\n# Run with verbose output\n.\u002Fgradlew test --info\n\n# Run with coverage\n.\u002Fgradlew koverHtmlReport\n\n# Run detekt (static analysis)\n.\u002Fgradlew detekt\n\n# Run ktlint (formatting check)\n.\u002Fgradlew ktlintCheck\n\n# Continuous testing\n.\u002Fgradlew test --continuous\n```\n\n### Best Practices\n\n**DO:**\n- Write tests FIRST (TDD)\n- Use Kotest's spec styles consistently across the project\n- Use MockK's `coEvery`\u002F`coVerify` for suspend functions\n- Use `runTest` for coroutine testing\n- Test behavior, not implementation\n- Use property-based testing for pure functions\n- Use `data class` test fixtures for clarity\n\n**DON'T:**\n- Mix testing frameworks (pick Kotest and stick with it)\n- Mock data classes (use real instances)\n- Use `Thread.sleep()` in coroutine tests (use `advanceTimeBy`)\n- Skip the RED phase in TDD\n- Test private functions directly\n- Ignore flaky tests\n\n### Integration with CI\u002FCD\n\n```yaml\n# GitHub Actions example\ntest:\n  runs-on: ubuntu-latest\n  steps:\n    - uses: actions\u002Fcheckout@v4\n    - uses: actions\u002Fsetup-java@v4\n      with:\n        distribution: 'temurin'\n        java-version: '21'\n\n    - name: Run tests with coverage\n      run: .\u002Fgradlew test koverXmlReport\n\n    - name: Verify coverage\n      run: .\u002Fgradlew koverVerify\n\n    - name: Upload coverage\n      uses: codecov\u002Fcodecov-action@v5\n      with:\n        files: build\u002Freports\u002Fkover\u002Freport.xml\n        token: ${{ secrets.CODECOV_TOKEN }}\n```\n\n**Remember**: Tests are documentation. They show how your Kotlin code is meant to be used. Use Kotest's expressive matchers to make tests readable and MockK for clean mocking of dependencies.\n","# Kotlin Testing Patterns\n\nComprehensive Kotlin testing patterns for writing reliable, maintainable tests following TDD methodology with Kotest and MockK.\n\n## When to Use\n\n- Writing new Kotlin functions or classes\n- Adding test coverage to existing Kotlin code\n- Implementing property-based tests\n- Following TDD workflow in Kotlin projects\n- Configuring Kover for code coverage\n\n## How It Works\n\n1. **Identify target code** — Find the function, class, or module to test\n2. **Write a Kotest spec** — Choose a spec style (StringSpec, FunSpec, BehaviorSpec) matching the test scope\n3. **Mock dependencies** — Use MockK to isolate the unit under test\n4. **Run tests (RED)** — Verify the test fails with the expected error\n5. **Implement code (GREEN)** — Write minimal code to pass the test\n6. **Refactor** — Improve the implementation while keeping tests green\n7. **Check coverage** — Run `.\u002Fgradlew koverHtmlReport` and verify 80%+ coverage\n\n## Examples\n\nThe following sections contain detailed, runnable examples for each testing pattern:\n\n### Quick Reference\n\n- **Kotest specs** — StringSpec, FunSpec, BehaviorSpec, DescribeSpec examples in [Kotest Spec Styles](#kotest-spec-styles)\n- **Mocking** — MockK setup, coroutine mocking, argument capture in [MockK](#mockk)\n- **TDD walkthrough** — Full RED\u002FGREEN\u002FREFACTOR cycle with EmailValidator in [TDD Workflow for Kotlin](#tdd-workflow-for-kotlin)\n- **Coverage** — Kover configuration and commands in [Kover Coverage](#kover-coverage)\n- **Ktor testing** — testApplication setup in [Ktor testApplication Testing](#ktor-testapplication-testing)\n\n### TDD Workflow for Kotlin\n\n#### The RED-GREEN-REFACTOR Cycle\n\n```\nRED     -> Write a failing test first\nGREEN   -> Write minimal code to pass the test\nREFACTOR -> Improve code while keeping tests green\nREPEAT  -> Continue with next requirement\n```\n\n#### Step-by-Step TDD in Kotlin\n\n```kotlin\n\u002F\u002F Step 1: Define the interface\u002Fsignature\n\u002F\u002F EmailValidator.kt\npackage com.example.validator\n\nfun validateEmail(email: String): Result\u003CString> {\n    TODO(\"not implemented\")\n}\n\n\u002F\u002F Step 2: Write failing test (RED)\n\u002F\u002F EmailValidatorTest.kt\npackage com.example.validator\n\nimport io.kotest.core.spec.style.StringSpec\nimport io.kotest.matchers.result.shouldBeFailure\nimport io.kotest.matchers.result.shouldBeSuccess\n\nclass EmailValidatorTest : StringSpec({\n    \"valid email returns success\" {\n        validateEmail(\"user@example.com\").shouldBeSuccess(\"user@example.com\")\n    }\n\n    \"empty email returns failure\" {\n        validateEmail(\"\").shouldBeFailure()\n    }\n\n    \"email without @ returns failure\" {\n        validateEmail(\"userexample.com\").shouldBeFailure()\n    }\n})\n\n\u002F\u002F Step 3: Run tests - verify FAIL\n\u002F\u002F $ .\u002Fgradlew test\n\u002F\u002F EmailValidatorTest > valid email returns success FAILED\n\u002F\u002F   kotlin.NotImplementedError: An operation is not implemented\n\n\u002F\u002F Step 4: Implement minimal code (GREEN)\nfun validateEmail(email: String): Result\u003CString> {\n    if (email.isBlank()) return Result.failure(IllegalArgumentException(\"Email cannot be blank\"))\n    if ('@' !in email) return Result.failure(IllegalArgumentException(\"Email must contain @\"))\n    val regex = Regex(\"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Za-z]{2,}$\")\n    if (!regex.matches(email)) return Result.failure(IllegalArgumentException(\"Invalid email format\"))\n    return Result.success(email)\n}\n\n\u002F\u002F Step 5: Run tests - verify PASS\n\u002F\u002F $ .\u002Fgradlew test\n\u002F\u002F EmailValidatorTest > valid email returns success PASSED\n\u002F\u002F EmailValidatorTest > empty email returns failure PASSED\n\u002F\u002F EmailValidatorTest > email without @ returns failure PASSED\n\n\u002F\u002F Step 6: Refactor if needed, verify tests still pass\n```\n\n### Kotest Spec Styles\n\n#### StringSpec (Simplest)\n\n```kotlin\nclass CalculatorTest : StringSpec({\n    \"add two positive numbers\" {\n        Calculator.add(2, 3) shouldBe 5\n    }\n\n    \"add negative numbers\" {\n        Calculator.add(-1, -2) shouldBe -3\n    }\n\n    \"add zero\" {\n        Calculator.add(0, 5) shouldBe 5\n    }\n})\n```\n\n#### FunSpec (JUnit-like)\n\n```kotlin\nclass UserServiceTest : FunSpec({\n    val repository = mockk\u003CUserRepository>()\n    val service = UserService(repository)\n\n    test(\"getUser returns user when found\") {\n        val expected = User(id = \"1\", name = \"Alice\")\n        coEvery { repository.findById(\"1\") } returns expected\n\n        val result = service.getUser(\"1\")\n\n        result shouldBe expected\n    }\n\n    test(\"getUser throws when not found\") {\n        coEvery { repository.findById(\"999\") } returns null\n\n        shouldThrow\u003CUserNotFoundException> {\n            service.getUser(\"999\")\n        }\n    }\n})\n```\n\n#### BehaviorSpec (BDD Style)\n\n```kotlin\nclass OrderServiceTest : BehaviorSpec({\n    val repository = mockk\u003COrderRepository>()\n    val paymentService = mockk\u003CPaymentService>()\n    val service = OrderService(repository, paymentService)\n\n    Given(\"a valid order request\") {\n        val request = CreateOrderRequest(\n            userId = \"user-1\",\n            items = listOf(OrderItem(\"product-1\", quantity = 2)),\n        )\n\n        When(\"the order is placed\") {\n            coEvery { paymentService.charge(any()) } returns PaymentResult.Success\n            coEvery { repository.save(any()) } answers { firstArg() }\n\n            val result = service.placeOrder(request)\n\n            Then(\"it should return a confirmed order\") {\n                result.status shouldBe OrderStatus.CONFIRMED\n            }\n\n            Then(\"it should charge payment\") {\n                coVerify(exactly = 1) { paymentService.charge(any()) }\n            }\n        }\n\n        When(\"payment fails\") {\n            coEvery { paymentService.charge(any()) } returns PaymentResult.Declined\n\n            Then(\"it should throw PaymentException\") {\n                shouldThrow\u003CPaymentException> {\n                    service.placeOrder(request)\n                }\n            }\n        }\n    }\n})\n```\n\n#### DescribeSpec (RSpec Style)\n\n```kotlin\nclass UserValidatorTest : DescribeSpec({\n    describe(\"validateUser\") {\n        val validator = UserValidator()\n\n        context(\"with valid input\") {\n            it(\"accepts a normal user\") {\n                val user = CreateUserRequest(\"Alice\", \"alice@example.com\")\n                validator.validate(user).shouldBeValid()\n            }\n        }\n\n        context(\"with invalid name\") {\n            it(\"rejects blank name\") {\n                val user = CreateUserRequest(\"\", \"alice@example.com\")\n                validator.validate(user).shouldBeInvalid()\n            }\n\n            it(\"rejects name exceeding max length\") {\n                val user = CreateUserRequest(\"A\".repeat(256), \"alice@example.com\")\n                validator.validate(user).shouldBeInvalid()\n            }\n        }\n    }\n})\n```\n\n### Kotest Matchers\n\n#### Core Matchers\n\n```kotlin\nimport io.kotest.matchers.shouldBe\nimport io.kotest.matchers.shouldNotBe\nimport io.kotest.matchers.string.*\nimport io.kotest.matchers.collections.*\nimport io.kotest.matchers.nulls.*\n\n\u002F\u002F Equality\nresult shouldBe expected\nresult shouldNotBe unexpected\n\n\u002F\u002F Strings\nname shouldStartWith \"Al\"\nname shouldEndWith \"ice\"\nname shouldContain \"lic\"\nname shouldMatch Regex(\"[A-Z][a-z]+\")\nname.shouldBeBlank()\n\n\u002F\u002F Collections\nlist shouldContain \"item\"\nlist shouldHaveSize 3\nlist.shouldBeSorted()\nlist.shouldContainAll(\"a\", \"b\", \"c\")\nlist.shouldBeEmpty()\n\n\u002F\u002F Nulls\nresult.shouldNotBeNull()\nresult.shouldBeNull()\n\n\u002F\u002F Types\nresult.shouldBeInstanceOf\u003CUser>()\n\n\u002F\u002F Numbers\ncount shouldBeGreaterThan 0\nprice shouldBeInRange 1.0..100.0\n\n\u002F\u002F Exceptions\nshouldThrow\u003CIllegalArgumentException> {\n    validateAge(-1)\n}.message shouldBe \"Age must be positive\"\n\nshouldNotThrow\u003CException> {\n    validateAge(25)\n}\n```\n\n#### Custom Matchers\n\n```kotlin\nfun beActiveUser() = object : Matcher\u003CUser> {\n    override fun test(value: User) = MatcherResult(\n        value.isActive && value.lastLogin != null,\n        { \"User ${value.id} should be active with a last login\" },\n        { \"User ${value.id} should not be active\" },\n    )\n}\n\n\u002F\u002F Usage\nuser should beActiveUser()\n```\n\n### MockK\n\n#### Basic Mocking\n\n```kotlin\nclass UserServiceTest : FunSpec({\n    val repository = mockk\u003CUserRepository>()\n    val logger = mockk\u003CLogger>(relaxed = true) \u002F\u002F Relaxed: returns defaults\n    val service = UserService(repository, logger)\n\n    beforeTest {\n        clearMocks(repository, logger)\n    }\n\n    test(\"findUser delegates to repository\") {\n        val expected = User(id = \"1\", name = \"Alice\")\n        every { repository.findById(\"1\") } returns expected\n\n        val result = service.findUser(\"1\")\n\n        result shouldBe expected\n        verify(exactly = 1) { repository.findById(\"1\") }\n    }\n\n    test(\"findUser returns null for unknown id\") {\n        every { repository.findById(any()) } returns null\n\n        val result = service.findUser(\"unknown\")\n\n        result.shouldBeNull()\n    }\n})\n```\n\n#### Coroutine Mocking\n\n```kotlin\nclass AsyncUserServiceTest : FunSpec({\n    val repository = mockk\u003CUserRepository>()\n    val service = UserService(repository)\n\n    test(\"getUser suspending function\") {\n        coEvery { repository.findById(\"1\") } returns User(id = \"1\", name = \"Alice\")\n\n        val result = service.getUser(\"1\")\n\n        result.name shouldBe \"Alice\"\n        coVerify { repository.findById(\"1\") }\n    }\n\n    test(\"getUser with delay\") {\n        coEvery { repository.findById(\"1\") } coAnswers {\n            delay(100) \u002F\u002F Simulate async work\n            User(id = \"1\", name = \"Alice\")\n        }\n\n        val result = service.getUser(\"1\")\n        result.name shouldBe \"Alice\"\n    }\n})\n```\n\n#### Argument Capture\n\n```kotlin\ntest(\"save captures the user argument\") {\n    val slot = slot\u003CUser>()\n    coEvery { repository.save(capture(slot)) } returns Unit\n\n    service.createUser(CreateUserRequest(\"Alice\", \"alice@example.com\"))\n\n    slot.captured.name shouldBe \"Alice\"\n    slot.captured.email shouldBe \"alice@example.com\"\n    slot.captured.id.shouldNotBeNull()\n}\n```\n\n#### Spy and Partial Mocking\n\n```kotlin\ntest(\"spy on real object\") {\n    val realService = UserService(repository)\n    val spy = spyk(realService)\n\n    every { spy.generateId() } returns \"fixed-id\"\n\n    spy.createUser(request)\n\n    verify { spy.generateId() } \u002F\u002F Overridden\n    \u002F\u002F Other methods use real implementation\n}\n```\n\n### Coroutine Testing\n\n#### runTest for Suspend Functions\n\n```kotlin\nimport kotlinx.coroutines.test.runTest\n\nclass CoroutineServiceTest : FunSpec({\n    test(\"concurrent fetches complete together\") {\n        runTest {\n            val service = DataService(testScope = this)\n\n            val result = service.fetchAllData()\n\n            result.users.shouldNotBeEmpty()\n            result.products.shouldNotBeEmpty()\n        }\n    }\n\n    test(\"timeout after delay\") {\n        runTest {\n            val service = SlowService()\n\n            shouldThrow\u003CTimeoutCancellationException> {\n                withTimeout(100) {\n                    service.slowOperation() \u002F\u002F Takes > 100ms\n                }\n            }\n        }\n    }\n})\n```\n\n#### Testing Flows\n\n```kotlin\nimport io.kotest.matchers.collections.shouldContainInOrder\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.advanceTimeBy\nimport kotlinx.coroutines.test.runTest\n\nclass FlowServiceTest : FunSpec({\n    test(\"observeUsers emits updates\") {\n        runTest {\n            val service = UserFlowService()\n\n            val emissions = service.observeUsers()\n                .take(3)\n                .toList()\n\n            emissions shouldHaveSize 3\n            emissions.last().shouldNotBeEmpty()\n        }\n    }\n\n    test(\"searchUsers debounces input\") {\n        runTest {\n            val service = SearchService()\n            val queries = MutableSharedFlow\u003CString>()\n\n            val results = mutableListOf\u003CList\u003CUser>>()\n            val job = launch {\n                service.searchUsers(queries).collect { results.add(it) }\n            }\n\n            queries.emit(\"a\")\n            queries.emit(\"ab\")\n            queries.emit(\"abc\") \u002F\u002F Only this should trigger search\n            advanceTimeBy(500)\n\n            results shouldHaveSize 1\n            job.cancel()\n        }\n    }\n})\n```\n\n#### TestDispatcher\n\n```kotlin\nimport kotlinx.coroutines.test.StandardTestDispatcher\nimport kotlinx.coroutines.test.advanceUntilIdle\n\nclass DispatcherTest : FunSpec({\n    test(\"uses test dispatcher for controlled execution\") {\n        val dispatcher = StandardTestDispatcher()\n\n        runTest(dispatcher) {\n            var completed = false\n\n            launch {\n                delay(1000)\n                completed = true\n            }\n\n            completed shouldBe false\n            advanceTimeBy(1000)\n            completed shouldBe true\n        }\n    }\n})\n```\n\n### Property-Based Testing\n\n#### Kotest Property Testing\n\n```kotlin\nimport io.kotest.core.spec.style.FunSpec\nimport io.kotest.property.Arb\nimport io.kotest.property.arbitrary.*\nimport io.kotest.property.forAll\nimport io.kotest.property.checkAll\nimport kotlinx.serialization.json.Json\nimport kotlinx.serialization.encodeToString\nimport kotlinx.serialization.decodeFromString\n\n\u002F\u002F Note: The serialization roundtrip test below requires the User data class\n\u002F\u002F to be annotated with @Serializable (from kotlinx.serialization).\n\nclass PropertyTest : FunSpec({\n    test(\"string reverse is involutory\") {\n        forAll\u003CString> { s ->\n            s.reversed().reversed() == s\n        }\n    }\n\n    test(\"list sort is idempotent\") {\n        forAll(Arb.list(Arb.int())) { list ->\n            list.sorted() == list.sorted().sorted()\n        }\n    }\n\n    test(\"serialization roundtrip preserves data\") {\n        checkAll(Arb.bind(Arb.string(1..50), Arb.string(5..100)) { name, email ->\n            User(name = name, email = \"$email@test.com\")\n        }) { user ->\n            val json = Json.encodeToString(user)\n            val decoded = Json.decodeFromString\u003CUser>(json)\n            decoded shouldBe user\n        }\n    }\n})\n```\n\n#### Custom Generators\n\n```kotlin\nval userArb: Arb\u003CUser> = Arb.bind(\n    Arb.string(minSize = 1, maxSize = 50),\n    Arb.email(),\n    Arb.enum\u003CRole>(),\n) { name, email, role ->\n    User(\n        id = UserId(UUID.randomUUID().toString()),\n        name = name,\n        email = Email(email),\n        role = role,\n    )\n}\n\nval moneyArb: Arb\u003CMoney> = Arb.bind(\n    Arb.long(1L..1_000_000L),\n    Arb.enum\u003CCurrency>(),\n) { amount, currency ->\n    Money(amount, currency)\n}\n```\n\n### Data-Driven Testing\n\n#### withData in Kotest\n\n```kotlin\nclass ParserTest : FunSpec({\n    context(\"parsing valid dates\") {\n        withData(\n            \"2026-01-15\" to LocalDate(2026, 1, 15),\n            \"2026-12-31\" to LocalDate(2026, 12, 31),\n            \"2000-01-01\" to LocalDate(2000, 1, 1),\n        ) { (input, expected) ->\n            parseDate(input) shouldBe expected\n        }\n    }\n\n    context(\"rejecting invalid dates\") {\n        withData(\n            nameFn = { \"rejects '$it'\" },\n            \"not-a-date\",\n            \"2026-13-01\",\n            \"2026-00-15\",\n            \"\",\n        ) { input ->\n            shouldThrow\u003CDateParseException> {\n                parseDate(input)\n            }\n        }\n    }\n})\n```\n\n### Test Lifecycle and Fixtures\n\n#### BeforeTest \u002F AfterTest\n\n```kotlin\nclass DatabaseTest : FunSpec({\n    lateinit var db: Database\n\n    beforeSpec {\n        db = Database.connect(\"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1\")\n        transaction(db) {\n            SchemaUtils.create(UsersTable)\n        }\n    }\n\n    afterSpec {\n        transaction(db) {\n            SchemaUtils.drop(UsersTable)\n        }\n    }\n\n    beforeTest {\n        transaction(db) {\n            UsersTable.deleteAll()\n        }\n    }\n\n    test(\"insert and retrieve user\") {\n        transaction(db) {\n            UsersTable.insert {\n                it[name] = \"Alice\"\n                it[email] = \"alice@example.com\"\n            }\n        }\n\n        val users = transaction(db) {\n            UsersTable.selectAll().map { it[UsersTable.name] }\n        }\n\n        users shouldContain \"Alice\"\n    }\n})\n```\n\n#### Kotest Extensions\n\n```kotlin\n\u002F\u002F Reusable test extension\nclass DatabaseExtension : BeforeSpecListener, AfterSpecListener {\n    lateinit var db: Database\n\n    override suspend fun beforeSpec(spec: Spec) {\n        db = Database.connect(\"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1\")\n    }\n\n    override suspend fun afterSpec(spec: Spec) {\n        \u002F\u002F cleanup\n    }\n}\n\nclass UserRepositoryTest : FunSpec({\n    val dbExt = DatabaseExtension()\n    register(dbExt)\n\n    test(\"save and find user\") {\n        val repo = UserRepository(dbExt.db)\n        \u002F\u002F ...\n    }\n})\n```\n\n### Kover Coverage\n\n#### Gradle Configuration\n\n```kotlin\n\u002F\u002F build.gradle.kts\nplugins {\n    id(\"org.jetbrains.kotlinx.kover\") version \"0.9.7\"\n}\n\nkover {\n    reports {\n        total {\n            html { onCheck = true }\n            xml { onCheck = true }\n        }\n        filters {\n            excludes {\n                classes(\"*.generated.*\", \"*.config.*\")\n            }\n        }\n        verify {\n            rule {\n                minBound(80) \u002F\u002F Fail build below 80% coverage\n            }\n        }\n    }\n}\n```\n\n#### Coverage Commands\n\n```bash\n# Run tests with coverage\n.\u002Fgradlew koverHtmlReport\n\n# Verify coverage thresholds\n.\u002Fgradlew koverVerify\n\n# XML report for CI\n.\u002Fgradlew koverXmlReport\n\n# View HTML report (use the command for your OS)\n# macOS:   open build\u002Freports\u002Fkover\u002Fhtml\u002Findex.html\n# Linux:   xdg-open build\u002Freports\u002Fkover\u002Fhtml\u002Findex.html\n# Windows: start build\u002Freports\u002Fkover\u002Fhtml\u002Findex.html\n```\n\n#### Coverage Targets\n\n| Code Type | Target |\n|-----------|--------|\n| Critical business logic | 100% |\n| Public APIs | 90%+ |\n| General code | 80%+ |\n| Generated \u002F config code | Exclude |\n\n### Ktor testApplication Testing\n\n```kotlin\nclass ApiRoutesTest : FunSpec({\n    test(\"GET \u002Fusers returns list\") {\n        testApplication {\n            application {\n                configureRouting()\n                configureSerialization()\n            }\n\n            val response = client.get(\"\u002Fusers\")\n\n            response.status shouldBe HttpStatusCode.OK\n            val users = response.body\u003CList\u003CUserResponse>>()\n            users.shouldNotBeEmpty()\n        }\n    }\n\n    test(\"POST \u002Fusers creates user\") {\n        testApplication {\n            application {\n                configureRouting()\n                configureSerialization()\n            }\n\n            val response = client.post(\"\u002Fusers\") {\n                contentType(ContentType.Application.Json)\n                setBody(CreateUserRequest(\"Alice\", \"alice@example.com\"))\n            }\n\n            response.status shouldBe HttpStatusCode.Created\n        }\n    }\n})\n```\n\n### Testing Commands\n\n```bash\n# Run all tests\n.\u002Fgradlew test\n\n# Run specific test class\n.\u002Fgradlew test --tests \"com.example.UserServiceTest\"\n\n# Run specific test\n.\u002Fgradlew test --tests \"com.example.UserServiceTest.getUser returns user when found\"\n\n# Run with verbose output\n.\u002Fgradlew test --info\n\n# Run with coverage\n.\u002Fgradlew koverHtmlReport\n\n# Run detekt (static analysis)\n.\u002Fgradlew detekt\n\n# Run ktlint (formatting check)\n.\u002Fgradlew ktlintCheck\n\n# Continuous testing\n.\u002Fgradlew test --continuous\n```\n\n### Best Practices\n\n**DO:**\n- Write tests FIRST (TDD)\n- Use Kotest's spec styles consistently across the project\n- Use MockK's `coEvery`\u002F`coVerify` for suspend functions\n- Use `runTest` for coroutine testing\n- Test behavior, not implementation\n- Use property-based testing for pure functions\n- Use `data class` test fixtures for clarity\n\n**DON'T:**\n- Mix testing frameworks (pick Kotest and stick with it)\n- Mock data classes (use real instances)\n- Use `Thread.sleep()` in coroutine tests (use `advanceTimeBy`)\n- Skip the RED phase in TDD\n- Test private functions directly\n- Ignore flaky tests\n\n### Integration with CI\u002FCD\n\n```yaml\n# GitHub Actions example\ntest:\n  runs-on: ubuntu-latest\n  steps:\n    - uses: actions\u002Fcheckout@v4\n    - uses: actions\u002Fsetup-java@v4\n      with:\n        distribution: 'temurin'\n        java-version: '21'\n\n    - name: Run tests with coverage\n      run: .\u002Fgradlew test koverXmlReport\n\n    - name: Verify coverage\n      run: .\u002Fgradlew koverVerify\n\n    - name: Upload coverage\n      uses: codecov\u002Fcodecov-action@v5\n      with:\n        files: build\u002Freports\u002Fkover\u002Freport.xml\n        token: ${{ secrets.CODECOV_TOKEN }}\n```\n\n**Remember**: Tests are documentation. They show how your Kotlin code is meant to be used. Use Kotest's expressive matchers to make tests readable and MockK for clean mocking of dependencies.\n",{"name":5,"origin":24,"description":7},"ECC","main","481dcd7dd2e288b9d627a2bd017db70e64e6c6fa","core\u002Fskills\u002Fkotlin-testing",2,"https:\u002F\u002Fgithub.com\u002FHoronomos-laps\u002FYana-AI","https:\u002F\u002Fyana.vutam.link\u002F","https:\u002F\u002Fgithub.com\u002FHoronomos-laps","https:\u002F\u002Fyana.vutam.link","An open-source governed runtime & execution environment for multi-agent AI systems.","2026-11-14 13:24:09",{"source":8,"skillName":5,"commands":36,"prompt":46,"zipAvailable":47},[37,40,43],{"tool":38,"command":39},"npx","npx skills add Horonomos-laps\u002FYana-AI --skill kotlin-testing",{"tool":41,"command":42},"bunx","bunx skills add Horonomos-laps\u002FYana-AI --skill kotlin-testing",{"tool":44,"command":45},"pnpm","pnpm dlx skills add Horonomos-laps\u002FYana-AI --skill kotlin-testing","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FHoronomos-laps\u002FYana-AI\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fkotlin-testing\u002FSKILL.md",true,[],[50,58,65,73,81,89],{"skillNo":51,"name":52,"nameZh":6,"description":53,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":54,"licenseFileUrl":6,"sourceHtmlUrl":55,"categories":56,"keywords":57,"publishedAt":19},"01M735GKET5YVYKAY62W2P5YXB","openai--sentry--sentry","Use when the user asks to inspect Sentry issues or events, summarize recent production errors, or pull basic Sentry health data via the Sentry API; perform read-only queries with the bundled script and require `SENTRY_AUTH_TOKEN`.","MIT","https:\u002F\u002Fgithub.com\u002FHoronomos-laps\u002FYana-AI\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fopenai--sentry--sentry\u002FSKILL.md",[],[],{"skillNo":59,"name":60,"nameZh":6,"description":61,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":62,"categories":63,"keywords":64,"publishedAt":19},"01M73DCAP83T17HY7SMGK7MGFE","ito-basket-compare","Compare Itô prediction-market baskets against a user's knowledge base, portfolio notes, financial context, watchlist, or research thesis. Use for read-only basket comparison and gap analysis without investment advice or live trading.","https:\u002F\u002Fgithub.com\u002FHoronomos-laps\u002FYana-AI\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fito-basket-compare\u002FSKILL.md",[],[],{"skillNo":66,"name":67,"nameZh":6,"description":68,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":69,"sourceHtmlUrl":70,"categories":71,"keywords":72,"publishedAt":19},"01M734CYEMZFDR3KH6SW0JP8TD","implementing-network-traffic-baselining","Build network traffic baselines from NetFlow\u002FIPFIX data using Python pandas for statistical analysis, z-score anomaly detection, and hourly\u002Fdaily traffic pattern profiling","https:\u002F\u002Fgithub.com\u002Fyanacuti1121\u002FYana-AI\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fimplementing-network-traffic-baselining\u002FLICENSE","https:\u002F\u002Fgithub.com\u002FHoronomos-laps\u002FYana-AI\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fimplementing-network-traffic-baselining\u002FSKILL.md",[],[],{"skillNo":74,"name":75,"nameZh":6,"description":76,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":77,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":19},"01M73ASZ6NJRJM44Z2P84NGQ3Q","detecting-evasion-techniques-in-endpoint-logs","Detects defense evasion techniques used by adversaries in endpoint logs including log tampering, timestomping, process injection, and security tool disabling. Use when investigating suspicious endpoint behavior, building detection rules for evasion tactics, or conducting threat hunting for stealthy adversary activity. Activates for requests involving evasion detection, defense evasion analysis, log tampering detection, or MITRE ATT&CK TA0005.","https:\u002F\u002Fgithub.com\u002Fyanacuti1121\u002FYana-AI\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fdetecting-evasion-techniques-in-endpoint-logs\u002FLICENSE","https:\u002F\u002Fgithub.com\u002FHoronomos-laps\u002FYana-AI\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fdetecting-evasion-techniques-in-endpoint-logs\u002FSKILL.md",[],[],{"skillNo":82,"name":83,"nameZh":6,"description":84,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":85,"sourceHtmlUrl":86,"categories":87,"keywords":88,"publishedAt":19},"01M734AFNXD06Z78QRK0VKGT7Q","implementing-microsegmentation-with-guardicore","Implementing microsegmentation using Akamai Guardicore Segmentation to map application dependencies, create granular network policies, visualize east-west traffic flows, and enforce least-privilege communication between workloads across data centers and cloud.","https:\u002F\u002Fgithub.com\u002Fyanacuti1121\u002FYana-AI\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fimplementing-microsegmentation-with-guardicore\u002FLICENSE","https:\u002F\u002Fgithub.com\u002FHoronomos-laps\u002FYana-AI\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fimplementing-microsegmentation-with-guardicore\u002FSKILL.md",[],[],{"skillNo":90,"name":91,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":92,"categories":93,"keywords":94,"publishedAt":19},"01M736TK27QMCXK0DVCH25SJN5","ramdisk-tmpfs-patterns","https:\u002F\u002Fgithub.com\u002FHoronomos-laps\u002FYana-AI\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Framdisk-tmpfs-patterns\u002FSKILL.md",[],[]]