Skills Plugins MCP Prompt Model 博客 我的中心
開発 #writing #testing

kotlin-testing

Kotlin testing patterns with Kotest, MockK, coroutine testing, property-based testing, and Kover coverage. Follows TDD methodology with idiomatic Kotlin practices. Use when writing Kotlin tests with Kotest or MockK, or testing coroutines and checking coverage.

DeepseekModel キュレーション済みスキル 品質 優秀 · 90 v1.0.0

取得

https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-skills-kotlin-testing-skill-md&format=skill
ダウンロード .skill 標準形式。system_prompt と model_config を収録し、任意の Agent で利用可能
.skill ファイルの system_prompt フィールドの実際の内容。
name kotlin-testing description Kotlin testing patterns with Kotest, MockK, coroutine testing, property-based testing, and Kover coverage. Follows TDD methodology with idiomatic Kotlin practices. Use when writing Kotlin tests with Kotest or MockK, or testing coroutines and checking coverage. metadata {"origin":"ECC"} Kotlin Testing Patterns Comprehensive Kotlin testing patterns for writing reliable, maintainable tests following TDD methodology with Kotest and MockK. When to Use Writing new Kotlin functions or classes Adding test coverage to existing Kotlin code Implementing property-based tests Following TDD workflow in Kotlin projects Configuring Kover for code coverage How It Works Identify target code — Find the function, class, or module to test Write a Kotest spec — Choose a spec style (StringSpec, FunSpec, BehaviorSpec) matching the test scope Mock dependencies — Use MockK to isolate the unit under test Run tests (RED) — Verify the test fails with the expected error Implement code (GREEN) — Write minimal code to pass the test Refactor — Improve the implementation while keeping tests green Check coverage — Run ./gradlew koverHtmlReport and verify 80%+ coverage Examples The following sections contain detailed, runnable examples for each testing pattern: Quick Reference Kotest specs — StringSpec, FunSpec, BehaviorSpec, DescribeSpec examples in Kotest Spec Styles Mocking — MockK setup, coroutine mocking, argument capture in MockK TDD walkthrough — Full RED/GREEN/REFACTOR cycle with EmailValidator in TDD Workflow for Kotlin Coverage — Kover configuration and commands in Kover Coverage Ktor testing — testApplication setup in Ktor testApplication Testing TDD Workflow for Kotlin The RED-GREEN-REFACTOR Cycle RED -> Write a failing test first GREEN -> Write minimal code to pass the test REFACTOR -> Improve code while keeping tests green REPEAT -> Continue with next requirement Step-by-Step TDD in Kotlin // Step 1: Define the interface/signature // EmailValidator.kt package com.example.validator fun validateEmail (email: String ) : Result<String> { TODO( "not implemented" ) } // Step 2: Write failing test (RED) // EmailValidatorTest.kt package com.example.validator import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.result.shouldBeFailure import io.kotest.matchers.result.shouldBeSuccess class EmailValidatorTest : StringSpec ({ "valid email returns success" { validateEmail( "user@example.com" ).shouldBeSuccess( "user@example.com" ) } "empty email returns failure" { validateEmail( "" ).shouldBeFailure() } "email without @ returns failure" { validateEmail( "userexample.com" ).shouldBeFailure() } }) // Step 3: Run tests - verify FAIL // $ ./gradlew test // EmailValidatorTest > valid email returns success FAILED // kotlin.NotImplementedError: An operation is not implemented // Step 4: Implement minimal code (GREEN) fun validateEmail (email: String ) : Result<String> { if (email.isBlank()) return Result.failure(IllegalArgumentException( "Email cannot be blank" )) if ( '@' ! in email) return Result.failure(IllegalArgumentException( "Email must contain @" )) val regex = Regex( "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$" ) if (!regex.matches(email)) return Result.failure(IllegalArgumentException( "Invalid email format" )) return Result.success(email) } // Step 5: Run tests - verify PASS // $ ./gradlew test // EmailValidatorTest > valid email returns success PASSED // EmailValidatorTest > empty email returns failure PASSED // EmailValidatorTest > email without @ returns failure PASSED // Step 6: Refactor if needed, verify tests still pass Kotest Spec Styles StringSpec (Simplest) class CalculatorTest : StringSpec ({ "add two positive numbers" { Calculator.add( 2 , 3 ) shouldBe 5 } "add negative numbers" { Calculator.add(- 1 , - 2 ) shouldBe - 3 } "add zero" { Calculator.add( 0 , 5 ) shouldBe 5 } }) FunSpec (JUnit-like) class UserServiceTest : FunSpec ({ val repository = mockk<UserRepository>() val service = UserService(repository) test( "getUser returns user when found" ) { val expected = User(id = "1" , name = "Alice" ) coEvery { repository.findById( "1" ) } returns expected val result = service.getUser( "1" ) result shouldBe expected } test( "getUser throws when not found" ) { coEvery { repository.findById( "999" ) } returns null shouldThrow<UserNotFoundException> { service.getUser( "999" ) } } }) BehaviorSpec (BDD Style) class OrderServiceTest : BehaviorSpec ({ val repository = mockk<OrderRepository>() val paymentService = mockk<PaymentService>() val service = OrderService(repository, paymentService) Given( "a valid order request" ) { val request = CreateOrderRequest( userId = "user-1" , items = listOf(OrderItem( "product-1" , quantity = 2 )), ) When( "the order is placed" ) { coEvery { paymentService.charge(any()) } returns PaymentResult.Success coEvery { repository.save(any()) } answers { firstArg() } val result = service.placeOrder(request) Then( "it should return a confirmed order" ) { result.status shouldBe OrderStatus.CONFIRMED } Then( "it should charge payment" ) { coVerify(exactly = 1 ) { paymentService.charge(any()) } } } When( "payment fails" ) { coEvery { paymentService.charge(any()) } returns PaymentResult.Declined Then( "it should throw PaymentException" ) { shouldThrow<PaymentException> { service.placeOrder(request) } } } } }) DescribeSpec (RSpec Style) 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() } } } }) Kotest Matchers Core Matchers 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.* // Equality 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() // Collections list shouldContain "item" list shouldHaveSize 3 list.shouldBeSorted() list.shouldContainAll( "a" , "b" , "c" ) list.shouldBeEmpty() // Nulls result.shouldNotBeNull() result.shouldBeNull() // Types result.shouldBeInstanceOf<User>() // Numbers count shouldBeGreaterThan 0 price shouldBeInRange 1.0 . .100 .0 // Exceptions shouldThrow<IllegalArgumentException> { validateAge(- 1 ) }.message shouldBe "Age must be positive" shouldNotThrow<Exception> { validateAge( 25 ) } Custom Matchers 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" }, ) } // Usage user should beActiveUser() MockK Basic Mocking class UserServiceTest : FunSpec ({ val repository = mockk<UserRepository>() val logger = mockk<Logger>(relaxed = true ) // Relaxed: returns defaults val service = UserService(repository, logger) beforeTest { clearMocks(repository, logger) } test( "findUser delegates to repository" ) { val expected = User(id = "1" , name = "Alice" ) every { repository.findById( "1" ) } returns expected val result = service.findUser( "1" ) result shouldBe expected verify(exactly = 1 ) { repository.findById( "1" ) } } test( "findUser returns null for unknown id" ) { every { repository.findById(any()) } returns null val result = service.findUser( "unknown" ) result.shouldBeNull() } }) Coroutine Mocking 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 ) // Simulate async work User(id = "1" , name = "Alice" ) } val result = service.getUser( "1" ) result.name shouldBe "Alice" } }) Argument Capture 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 and Partial Mocking 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() } // Overridden // Other methods use real implementation } Coroutine Testing runTest for Suspend Functions 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() // Takes > 100ms } } } } }) Testing 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" ) // Only this should trigger search 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 } } }) Property-Based Testing Kotest Property Testing 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 // Note: The serialization roundtrip test below requires the User data class // to be annotated with @Serializable (from kotlinx.serialization).
このスキルを起動するキーワード。クリックでコピーできます。

このスキルにはトリガーワードがありません。

ダウンロードした .skill に含まれるフィールド。
フィールド 説明
formatフォーマット識別子(skill/v1)
skill_idスキル固有 ID
nameスキル名
versionバージョン
description説明
categoryカテゴリ(配列)
trigger_wordsトリガーワード
tagsタグ
sourceソース
source_urlソース URL(本ページ)
exported_atエクスポート日時(ダウンロード毎)
system_promptシステムプロンプト本文
model_configモデル設定:provider / model / temperature / max_tokens / top_p
examplesサンプル
install_guide各プラットフォームの導入説明(Coze / Dify / Claude / カスタム)
同じスキルを各プラットフォーム形式で出力できます。
.skill 標準形式。system_prompt と model_config を収録し、任意の Agent で利用可能 ダウンロード
.skillpro 拡張形式。scripts / tools / dependencies / hooks を含む ダウンロード
.json 純粋な JSON 出力。system_prompt とモデル設定のみ ダウンロード
Coze frontmatter 付き Markdown。Coze へのインポート用 ダウンロード
Dify Dify DSL。アプリ作成後にそのままインポート ダウンロード

每日精选 Skill 推荐,免费送到你邮箱

输入邮箱,每天接收一个精选 AI Agent 技能推荐。完全免费,持续更新。

验证码 --

提交后我们会发送一封确认邮件,点击邮件里的链接才会开始收信。

完全免费,取消任意时间。我们不会发送垃圾邮件。