SKILL.md 18 KB


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:

  • fake
  • mock
  • stub
  • spy

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:

  1. Testing summary

    • overall strategy
    • test pyramid shape
    • local / Robolectric / instrumented / UI / screenshot split
    • shared-vs-platform test placement
  2. What is structurally sound

    • concrete strengths only
  3. 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
  4. Severity for each issue

    • high / medium / low
  5. 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
  6. Suggested target structure

    • proposed test-layer split if useful
  7. 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