Writing Kotlin Tests
Use this skill to choose the right test level first, then write compact contract-focused Kotlin tests.
Use kotlin.test APIs for test annotations and assertions. Use MockK when interaction matters. Do not use JUnit assertion or annotation APIs, Mockito, AssertJ, Hamcrest, Truth, or another assertion library unless an existing framework assertion requires it. For E2E JSON body checks, use jsonPath or parse JSON and assert fields with kotlin.test; Hamcrest matchers are allowed only inside an existing jsonPath assertion API.
Workflow
- Read the target code, nearby tests, build configuration, Kotlin version, platform, and source set before writing anything.
- Decide the lowest useful test level: unit, integration, or E2E.
- Load the matching reference only when needed:
- Unit tests: references/unit.md
- Integration tests: references/integration.md
- E2E tests: references/e2e.md
- Identify the public contract and add the smallest useful test set.
- Avoid duplicate coverage across levels.
- Put unit and integration tests in the module and source set that own the behavior.
- Run the narrowest relevant test command first, then broader tests when risk justifies it.
Test Levels
Choose the lowest useful level:
- Domain value object, entity helper, use-case branch, validation rule, coroutine transformation, repository adapter call shape: unit test.
- JPA entity mapping, repository query, transaction/lifecycle behavior, Spring bean wiring: integration test.
- HTTP route, serialization, security filter, controller-service-use-case-repository flow: E2E test.
Avoid duplicate coverage:
- If E2E verifies HTTP -> repository -> domain -> JSON, do not also assert every mapped domain field in adapter unit tests.
- Keep adapter unit tests only for adapter-specific policy, such as forcing
activeOnly = true on repository calls.
- Keep entity unit tests for pure helper contracts such as
isDeleted() and setDeleted().
Dependencies
- Use the project's existing
kotlin-test integration and test runner. Do not change runner adapters merely to replace source imports.
- Import annotations and assertions from
kotlin.test, such as Test, BeforeTest, assertEquals, assertContentEquals, assertFailsWith, assertIs, and assertNotNull.
- Use MockK, not Mockito, for mocks, stubs, spies, or interaction verification.
- Use
kotlinx-coroutines-test only when coroutine timing, dispatchers, cancellation, or virtual time are part of the contract and the project already provides it or the test requires it.
Test Fixtures
- Use Gradle
java-test-fixtures only when fixtures must be shared across modules; Kotlin fixtures belong under src/testFixtures/kotlin.
- Prefer fixtures in the adapter module that owns the fixture type.
- App-level E2E or integration tests may depend on
testFixtures(project(":<adapter-module>")) when they need adapter-owned entity fixtures.
- Do not create a separate fixture module for a single adapter's test data.
- Prefer small factory functions with named arguments and useful defaults over builders that only assign properties.
Common Structure
Keep tests flat when the class has one meaningful behavior surface. Split a large test class by behavior or public entry point instead of depending on JUnit-specific @Nested groups.
Place successful or valid cases first, then boundary cases, then invalid or exception cases.
Use @BeforeTest when multiple tests repeat the same setup. Keep shared preconditions in setup and behavior-specific values inside the test function.
Naming
- Prefer descriptive backtick function names when the repository permits them; otherwise follow its established
should... convention.
- Korean backtick test names must end with
~해야 한다 or ~이어야 한다.
- Use
shouldBeRejectedWhen... for invalid inputs when identifier-style names are required.
- Use
shouldBeReturned...When... for getter or helper return behavior.
- Use
shouldBeThrownWhen... for expected exception paths.
- Always write E2E with uppercase
E2E in class and file names, such as UserProfileE2ETest.
Assertions
- Prefer one action per test and assert only its observable result.
- Use structural equality or several focused
kotlin.test assertions when one action produces multiple observable properties.
- Use
assertFailsWith<ExpectedException> for exception contracts.
- Use
assertContentEquals for arrays and sequences whose ordered contents are the contract.
- Avoid asserting exception messages unless the message is part of the public contract.
- Do not use JUnit assertions through fully qualified calls or aliases.
Kotlin Nullability
Treat Kotlin types as the source of truth.
- Do not write
null tests for non-null Kotlin parameters, properties, or return types; such calls are outside the Kotlin contract.
- Cover
null explicitly for nullable types, including the policy encoded by the production code.
- Do not use
null as T, reflection, or Java interop only to bypass the compiler unless Java-callable misuse defense is itself the public contract.
- For Java platform types, make nullability explicit at the production boundary before testing internal behavior. Do not spread
!! into tests.
- Keep test doubles and fixtures at least as strict as the production nullability contract.
Review Pass
Before finishing:
- Check the chosen test level is the lowest useful level.
- Check level-specific reference rules were followed.
- Check success cases appear before failure cases.
- Check imports use
kotlin.test, not JUnit test or assertion APIs.
- Check MockK is used instead of Mockito and only where a fake would be less clear.
- Check tests cover the intended contract without duplicating higher-level coverage.
- Check
null tests exist only for nullable contracts or explicit Java-interoperability misuse defense.
- Check E2E class and file names use uppercase
E2E.
- Check E2E JSON assertions target fields through
jsonPath or parsed JSON, not raw string contains.
- Run the narrow test command and report the result.
1---2name: writing-kotlin-tests3description: Write, improve, or review Kotlin tests with kotlin-test, MockK, Spring Boot, and Spring Data JPA across unit, integration, and E2E levels. Use only for Kotlin test work, including domain, use-case, entity, validation, repository, adapter, coroutine, HTTP API, nullability, or Kotlin test-level selection decisions.4---56# Writing Kotlin Tests78Use this skill to choose the right test level first, then write compact contract-focused Kotlin tests.910Use `kotlin.test` APIs for test annotations and assertions. Use MockK when interaction matters. Do not use JUnit assertion or annotation APIs, Mockito, AssertJ, Hamcrest, Truth, or another assertion library unless an existing framework assertion requires it. For E2E JSON body checks, use `jsonPath` or parse JSON and assert fields with `kotlin.test`; Hamcrest matchers are allowed only inside an existing `jsonPath` assertion API.1112## Workflow13141. Read the target code, nearby tests, build configuration, Kotlin version, platform, and source set before writing anything.152. Decide the lowest useful test level: unit, integration, or E2E.163. Load the matching reference only when needed:17 - Unit tests: [references/unit.md](references/unit.md)18 - Integration tests: [references/integration.md](references/integration.md)19 - E2E tests: [references/e2e.md](references/e2e.md)204. Identify the public contract and add the smallest useful test set.215. Avoid duplicate coverage across levels.226. Put unit and integration tests in the module and source set that own the behavior.237. Run the narrowest relevant test command first, then broader tests when risk justifies it.2425## Test Levels2627Choose the lowest useful level:2829- Domain value object, entity helper, use-case branch, validation rule, coroutine transformation, repository adapter call shape: unit test.30- JPA entity mapping, repository query, transaction/lifecycle behavior, Spring bean wiring: integration test.31- HTTP route, serialization, security filter, controller-service-use-case-repository flow: E2E test.3233Avoid duplicate coverage:3435- If E2E verifies HTTP -> repository -> domain -> JSON, do not also assert every mapped domain field in adapter unit tests.36- Keep adapter unit tests only for adapter-specific policy, such as forcing `activeOnly = true` on repository calls.37- Keep entity unit tests for pure helper contracts such as `isDeleted()` and `setDeleted()`.3839## Dependencies4041- Use the project's existing `kotlin-test` integration and test runner. Do not change runner adapters merely to replace source imports.42- Import annotations and assertions from `kotlin.test`, such as `Test`, `BeforeTest`, `assertEquals`, `assertContentEquals`, `assertFailsWith`, `assertIs`, and `assertNotNull`.43- Use MockK, not Mockito, for mocks, stubs, spies, or interaction verification.44- Use `kotlinx-coroutines-test` only when coroutine timing, dispatchers, cancellation, or virtual time are part of the contract and the project already provides it or the test requires it.4546## Test Fixtures4748- Use Gradle `java-test-fixtures` only when fixtures must be shared across modules; Kotlin fixtures belong under `src/testFixtures/kotlin`.49- Prefer fixtures in the adapter module that owns the fixture type.50- App-level E2E or integration tests may depend on `testFixtures(project(":<adapter-module>"))` when they need adapter-owned entity fixtures.51- Do not create a separate fixture module for a single adapter's test data.52- Prefer small factory functions with named arguments and useful defaults over builders that only assign properties.5354## Common Structure5556Keep tests flat when the class has one meaningful behavior surface. Split a large test class by behavior or public entry point instead of depending on JUnit-specific `@Nested` groups.5758Place successful or valid cases first, then boundary cases, then invalid or exception cases.5960Use `@BeforeTest` when multiple tests repeat the same setup. Keep shared preconditions in setup and behavior-specific values inside the test function.6162## Naming6364- Prefer descriptive backtick function names when the repository permits them; otherwise follow its established `should...` convention.65- Korean backtick test names must end with `~해야 한다` or `~이어야 한다`.66- Use `shouldBeRejectedWhen...` for invalid inputs when identifier-style names are required.67- Use `shouldBeReturned...When...` for getter or helper return behavior.68- Use `shouldBeThrownWhen...` for expected exception paths.69- Always write E2E with uppercase `E2E` in class and file names, such as `UserProfileE2ETest`.7071## Assertions7273- Prefer one action per test and assert only its observable result.74- Use structural equality or several focused `kotlin.test` assertions when one action produces multiple observable properties.75- Use `assertFailsWith<ExpectedException>` for exception contracts.76- Use `assertContentEquals` for arrays and sequences whose ordered contents are the contract.77- Avoid asserting exception messages unless the message is part of the public contract.78- Do not use JUnit assertions through fully qualified calls or aliases.7980## Kotlin Nullability8182Treat Kotlin types as the source of truth.8384- Do not write `null` tests for non-null Kotlin parameters, properties, or return types; such calls are outside the Kotlin contract.85- Cover `null` explicitly for nullable types, including the policy encoded by the production code.86- Do not use `null as T`, reflection, or Java interop only to bypass the compiler unless Java-callable misuse defense is itself the public contract.87- For Java platform types, make nullability explicit at the production boundary before testing internal behavior. Do not spread `!!` into tests.88- Keep test doubles and fixtures at least as strict as the production nullability contract.8990## Review Pass9192Before finishing:9394- Check the chosen test level is the lowest useful level.95- Check level-specific reference rules were followed.96- Check success cases appear before failure cases.97- Check imports use `kotlin.test`, not JUnit test or assertion APIs.98- Check MockK is used instead of Mockito and only where a fake would be less clear.99- Check tests cover the intended contract without duplicating higher-level coverage.100- Check `null` tests exist only for nullable contracts or explicit Java-interoperability misuse defense.101- Check E2E class and file names use uppercase `E2E`.102- Check E2E JSON assertions target fields through `jsonPath` or parsed JSON, not raw string `contains`.103- Run the narrow test command and report the result.