| name | kotlin-testing-kmp |
| description | Use when designing, implementing, or reviewing tests in KMP projects — unit tests, instrumented tests, Compose Multiplatform UI tests, test doubles, test strategy, stability, performance, and screenshot testing. |
| license | Apache-2.0 |
| metadata | {"author":"Mariano Miani","version":"1.2.0"} |
Kotlin Multiplatform Testing
Use this skill when designing, implementing, or reviewing tests in a Kotlin Multiplatform project.
This skill is intentionally strict. Its purpose is to keep the test suite fast, trustworthy, behavior-focused, and aligned with Android’s testing guidance while still respecting shared KMP boundaries and Compose Multiplatform testing patterns.
Primary goals
The testing strategy should optimize for:
- strong confidence in business logic
- fast feedback from local/unit tests
- clear separation between local, Robolectric, instrumented, and UI tests
- behavior-focused tests instead of implementation-detail tests
- appropriate use of test doubles
- stable and performant larger tests
- isolated testing of shared KMP logic
- practical Compose Multiplatform UI test coverage
- targeted screenshot testing where visual regressions matter
Do not optimize for raw coverage percentages alone.
Optimize for confidence, speed, signal quality, and maintainability.
Official defaults to prefer
Unless the project has a strong reason not to, prefer these defaults:
- many small local/unit tests
- fewer integration tests
- fewer end-to-end/UI tests than lower-level tests
- tests focused on user-visible behavior and business outcomes
- use of test doubles to isolate dependencies
kotlin.test for assertions and test declarations in shared KMP test source sets — this is the cross-platform test API recommended by JetBrains and works across JVM, native, and JS/Wasm targets
- local tests in
src/test (JVM-side tests in Android modules)
- Android instrumented tests in
src/androidTest
- Robolectric only when Android framework behavior is needed but device execution is not necessary
- common Compose Multiplatform UI tests where the behavior is truly shared
- stable UI tests with clear synchronization and controlled environment setup
- screenshot tests for important visual surfaces when appearance regressions matter
Test strategy defaults
1. Testing pyramid
Prefer a pyramid-shaped suite:
- many unit tests
- fewer integration tests
- fewer UI/end-to-end tests
Review expectation:
- lower-level tests should catch most regressions
- larger tests should validate integration and user workflows, not replace unit coverage
Flag as a concern when:
- most confidence depends on slow UI tests
- business logic is only exercised through end-to-end flows
- the suite is top-heavy and expensive to run
2. Behavior over implementation detail
Prefer tests that validate:
- observable outputs
- state transitions
- user-visible effects
- business rules
Be cautious with tests that lock down:
- internal private structure
- implementation-specific sequencing that users do not observe
- framework internals
Flag as a concern when:
- tests fail after harmless refactors
- mocks assert incidental calls instead of real behavior
- UI tests are verifying widget internals rather than actual interaction outcomes
Test-scope review dimensions
3. What should be tested first
Prioritize tests for:
- business rules
- domain/use-case behavior
- repository coordination logic
- DTO/domain/UI mapping
- state-holder transitions
- failure and retry handling
- navigation decision logic where important
- shared UI behavior that is meaningful across targets
Flag as a concern when:
- trivial pass-through code is heavily tested while important rules are not
- mapping and error paths are untested
- state transitions are inferred rather than verified
4. Local unit tests
Local tests should be the default choice for fast feedback.
For shared KMP modules, tests in commonTest (using kotlin.test) run on all declared targets — JVM, native, JS/Wasm — making them the correct layer for shared business logic. Do not confuse commonTest (shared KMP tests) with src/test (Android/JVM-local tests).
Check whether:
- pure Kotlin/shared logic is tested in
commonTest using kotlin.test, not pushed into Android-specific test layers
- Android-specific behavior is tested in
src/test (local JVM) or src/androidTest (instrumented), not in shared test source sets
- test setup avoids Android device/emulator dependency when unnecessary
- most business logic tests live in the lowest practical test layer
Review expectations:
- shared logic in KMP should be exercised in
commonTest first, before platform-specific layers
src/test remains the main home for fast-running JVM-side tests on Android-only modules
- local tests are preferred unless the behavior truly needs Android runtime support
Flag as a concern when:
- shared business logic is only tested in Android-specific layers (
src/androidTest) without reason
- the suite pays emulator/device cost for logic that could be a
commonTest unit test
kotlin.test is not used in shared source sets — shared tests depend on JUnit directly, breaking non-JVM targets
4a. kotlin.test for shared source sets
In KMP projects, shared test source sets (e.g., commonTest) should use kotlin.test for assertions and test structure. kotlin.test provides @Test, assertEquals, assertNotNull, assertFailsWith, and other essentials that compile correctly for all KMP targets (JVM, native, JS, Wasm).
Check whether:
- shared test code uses
kotlin.test rather than JUnit or platform-specific assertion libraries
kotlin.test is declared as a dependency in the commonTest source set
- platform-specific test libraries (JUnit, XCTest wrappers) are added only in platform-specific test source sets when needed
Flag as a concern when:
- JUnit annotations or assertions appear in
commonTest without a JVM-only source set constraint
- shared tests fail on native or JS targets because of JVM-specific test infrastructure
kotlin.test is absent from a KMP project's test dependencies despite having shared business logic
5. Robolectric usage
Robolectric is an Android-only testing tool. It is not available in KMP shared test source sets — it can only be used in Android-specific test source sets (src/test in an Android module, or an androidUnitTest source set in a KMP module). Do not attempt to configure Robolectric in commonTest.
Robolectric is useful when Android-dependent behavior must be exercised on the JVM without a real device or emulator.
Check whether:
- Robolectric is used for Android framework interactions that do not require full device/emulator fidelity
- it is placed in Android-specific test source sets, not shared KMP test source sets
- it is not used as a default for tests that could be plain local tests or
kotlin.test unit tests
- the project uses it intentionally rather than as a catch-all compromise
Flag as a concern when:
- Robolectric is used for pure business logic that does not interact with Android APIs
- device-only behavior is assumed to be fully proven by Robolectric alone
- the suite becomes slow and brittle because Robolectric is overused
- Robolectric dependencies appear in shared KMP source sets
6. Instrumented tests
Instrumented tests should cover behavior that genuinely needs a real Android runtime, emulator, or device.
Check whether:
- Android integration behavior is validated in
src/androidTest
- tests that need framework/runtime fidelity are placed here
- instrumented tests are selective rather than the default
Good candidates:
- platform integration
- app-component interactions
- behavior that depends on real Android runtime semantics
- high-value UI flows
Flag as a concern when:
- most feature validation lives only in instrumented tests
- instrumented tests are used for logic that should be local
- test layering is blurry and costly
7. Compose Multiplatform UI tests
Compose Multiplatform supports shared UI testing.
Check whether:
- shared UI behavior is tested in common code when it is genuinely shared
runComposeUiTest is used for common Compose Multiplatform UI tests (note: as of mid-2025, runComposeUiTest requires @OptIn(ExperimentalTestApi::class); verify stability status against the current Compose Multiplatform release)
- platform-specific setup is added only when required
- shared UI tests focus on semantics and observable behavior
Flag as a concern when:
- shared UI can only be validated through Android-only tests without reason
- common UI tests are skipped despite heavily shared Compose behavior
- tests depend too much on implementation details instead of semantics
8. AndroidX Test setup discipline
AndroidX Test setup should be coherent and intentional.
Check whether:
- test runners, rules, and AndroidX Test dependencies are configured consistently
- instrumented tests use the standard test infrastructure instead of ad hoc setup
- test environment setup is centralized enough to avoid drift
Flag as a concern when:
- instrumented test setup differs arbitrarily across modules
- runner/rule configuration is duplicated or inconsistent
- test infrastructure itself becomes hard to trust
Test doubles
9. Appropriate test-double choice
Choose test doubles deliberately.
Common categories:
Prefer:
- fakes for repositories, data sources, and meaningful behavior simulation
- stubs for simple fixed responses
- mocks only when interaction verification is actually the point
- spies sparingly
Flag as a concern when:
- everything is mocked by default
- tests are interaction-heavy but behavior-light
- a fake would express the scenario more clearly than a deep mock tree
10. Dependency isolation
Check whether:
- tests isolate external systems appropriately
- network, database, file, and platform dependencies are replaced when the test does not need them
- doubles reduce flakiness and improve speed
Flag as a concern when:
- tests depend on real external services unnecessarily
- fake/test data behavior diverges so much from production that the test misleads
- the chosen double makes the test harder to understand
Stability and performance for larger tests
11. Big-test stability
Android’s guidance treats stability as a first-class quality attribute for larger tests.
Check whether:
- tests control asynchronous work predictably
- environment setup is repeatable
- test state is isolated between runs
- flakiness sources are identified and reduced
- retries are not hiding real nondeterminism
Flag as a concern when:
- UI/integration tests pass only intermittently
- timing assumptions replace synchronization
- global shared state leaks between tests
12. Performance of instrumented tests
Instrumented tests should be optimized because they are expensive.
Check whether:
- the instrumented suite stays focused on high-value scenarios
- setup and teardown are not wasteful
- large tests are not duplicated unnecessarily across many layers
- performance-sensitive test suites are monitored and trimmed
Flag as a concern when:
- large tests are used where local tests would suffice
- startup/setup costs dominate every test
- the suite becomes too slow to run regularly
UI testing guidance
13. UI tests should validate behavior
UI tests should focus on what the user can do and observe.
Check whether:
- assertions reflect visible behavior or meaningful semantics
- user actions are modeled realistically
- UI tests validate flows, not incidental structure
Flag as a concern when:
- tests lock onto fragile implementation details
- the suite checks internal tree shapes with little user value
- behavior is under-tested while low-value rendering details dominate
14. Screenshot testing
Screenshot tests are useful for detecting visual regressions on stable surfaces.
Check whether:
- screenshots are applied to important visual states
- expected variants are intentional and controlled
- screenshots complement, rather than replace, behavioral tests
- visual baselines are maintained carefully
Good candidates:
- design-system components
- stable destination screens
- major adaptive-layout variants
- key theme or locale states where appearance matters
Flag as a concern when:
- screenshot tests are used as the main correctness signal for behavior
- baselines churn constantly because surfaces are too unstable
- too many low-value screenshots make maintenance noisy
KMP-specific review dimensions
15. Shared-vs-platform test placement
Test-source-set placement should mirror code-source-set placement:
commonTest (using kotlin.test): shared business logic, domain rules, shared repository behavior, Compose Multiplatform UI behavior
- Android
src/test (JUnit/Robolectric): Android-specific logic, ViewModel behavior where ViewModel is Android-only
- Android
src/androidTest: Android integration, real framework behavior, UI flows on Android
- iOS/native test source sets: iOS-platform-specific behavior
Check whether:
- shared behavior is tested in
commonTest using kotlin.test
- Android-specific behavior remains in Android test layers
- platform-specific assertions do not leak into shared tests without reason
kotlin.test is used consistently in shared source sets (not JUnit4/JUnit5 directly, which are JVM-only)
Flag as a concern when:
- shared logic is only tested in Android-specific layers
- JUnit annotations appear in
commonTest (breaks non-JVM compilation targets)
- platform-specific test setup (context, activity, application) pollutes shared test code
- the team does not know which test layer owns which behavior
16. State-holder and flow testing
Check whether:
- state-holder outputs are tested deterministically
- loading, success, empty, error, retry, and partial-data paths are covered
- one-time events are tested separately from persistent state
- flow-based logic is verified without over-relying on UI tests
Flag as a concern when:
- state behavior is inferred only through UI rendering
- event emission is untested
- async/state tests are timing-based rather than deterministic
17. Repository and mapping testing
Check whether:
- repositories are tested for coordination logic, source-of-truth behavior, and error handling
- mapping is tested directly
- test doubles are used where they provide clarity
Flag as a concern when:
- mapping bugs can only be caught through broad integration tests
- repository conflict resolution is untested
- data-layer correctness depends mainly on manual QA
Severity framework
High severity
Likely to undermine trust in the suite.
Examples:
- business logic only covered by UI/instrumented tests
- highly flaky big tests
- no clear separation between local and instrumented tests
- shared KMP logic untested or only tested through one platform
- unstable UI tests driven by timing assumptions
Medium severity
Workable, but likely to create maintenance cost.
Examples:
- overuse of mocks
- Robolectric used too broadly
- screenshot coverage is noisy or poorly targeted
- instrumented suite too large for its value
- important state transitions missing direct tests
Low severity
Structurally acceptable but worth improving.
Examples:
- test naming could be clearer
- some fakes could replace mocks
- preview/screenshot variants could be better targeted
Required output format
When performing the review, respond with:
-
Testing summary
- overall strategy
- test pyramid shape
- local / Robolectric / instrumented / UI / screenshot split
- shared-vs-platform test placement
-
What is structurally sound
-
Issues by review dimension
- strategy and pyramid
- what is being tested
- local tests
- Robolectric usage
- instrumented tests
- Compose Multiplatform UI tests
- AndroidX Test setup
- test doubles
- stability
- performance
- UI behavior tests
- screenshot tests
- shared-vs-platform placement
- state-holder/flow tests
- repository/mapping tests
-
Severity for each issue
-
Concrete recommendations
- which tests should move down the pyramid
- where to replace mocks with fakes/stubs
- what should become local vs instrumented
- what shared UI should be covered in common tests
- where screenshot tests add value
- how to reduce flakiness and runtime
-
Suggested target structure
- proposed test-layer split if useful
-
Open risks
- migration cost
- flakiness still to investigate
- platform-specific validation still required
Tone
Be direct and practical.
Do not praise a large suite just because it has many tests.
If the testing strategy is weak, say why clearly.
Anti-patterns to flag aggressively
- top-heavy test suites dominated by UI/end-to-end tests
- business logic verified only through instrumented/UI tests
- unstable tests that rely on sleeps or timing guesses
- heavy mock usage where fakes would be clearer
- Android-specific assumptions inside shared KMP tests
- screenshot tests used as a substitute for behavior tests
- duplicated large tests across multiple layers
- expensive instrumented tests with low signal
References