How do you write shared tests for a KMP module, and what role does `commonTest` and the `kotlin.test` library play?
answer
- commonTest mirrors commonMain, depends on it
- kotlin("test") dependency
- @Test/@BeforeTest + assertEquals/assertFailsWith
- Maps to JUnit / Native runner / JS per target
- allTests vs jvmTest
basics
~10 sPut shared tests in commonTest and use the kotlin.test library. Those tests run once per target, so a single test verifies behavior on JVM, iOS, JS, etc.
solid answer
~40 s`commonTest` is the root *test* source set, the mirror of `commonMain`. Tests written there are compiled and executed for every target, so one test suite validates shared logic everywhere. You use the multiplatform `kotlin.test` library, which provides target-agnostic annotations and assertions — `@Test`, `@BeforeTest`, `@AfterTest`, `@Ignore`, plus `assertEquals`, `assertTrue`, `assertFailsWith`, `assertNull`. At build time `kotlin.test` maps onto each platform's real runner (JUnit on JVM, Kotlin/Native's test runner, etc.). Add it with `commonTest.dependencies { implementation(kotlin("test")) }`. `commonTest` depends on `commonMain` automatically, so it sees all common declarations. Platform-specific tests can still live in `jvmTest`, `iosTest`, etc., for behavior you can only assert on one platform.
code
kotlin · 16 lines// build.gradle.kts
kotlin {
sourceSets {
commonTest.dependencies {
implementation(kotlin("test"))
}
}
}
// src/commonTest/kotlin/CalcTest.kt
import kotlin.test.Test
import kotlin.test.assertEquals
class CalcTest {
@Test fun adds() = assertEquals(4, 2 + 2)
}go deeper
Knows shared tests go in commonTest and uses kotlin.test annotations.
Explains per-target execution, the kotlin("test") dependency, and that kotlin.test maps onto each platform's runner.
Discusses when to drop to platform test source sets and how coroutine tests (runTest) work in common.
Designs a testing strategy maximizing common coverage while reserving platform tests for genuinely platform-bound behavior, and reasons about CI matrix cost across targets.
## `commonTest`: the shared test source set Every KMP module has a parallel test hierarchy mirroring the production one. `commonTest` (`src/commonTest/kotlin`) is the root test source set and is the test-side counterpart of `commonMain`. It **automatically depends on `commonMain`**, so all your shared declarations are visible. Tests written here are compiled and run **once per target**, meaning a single assertion is verified on the JVM, on iOS, on JS — anywhere your module compiles. ## The `kotlin.test` library `kotlin.test` is the multiplatform testing API. It is added with the `kotlin("test")` helper: ```kotlin kotlin { sourceSets { commonTest.dependencies { implementation(kotlin("test")) } } } ``` It provides **target-agnostic** annotations and assertions: - **Annotations:** `@Test`, `@BeforeTest`, `@AfterTest`, `@Ignore`, `@BeforeClass`/`@AfterClass` equivalents per platform. - **Assertions:** `assertEquals`, `assertNotEquals`, `assertTrue`, `assertFalse`, `assertNull`, `assertNotNull`, `assertSame`, `assertFailsWith<T>`, `fail()`. Under the hood, `kotlin.test` **maps to the real runner of each platform**: JUnit (4 or 5) on the JVM, the Kotlin/Native test infrastructure on native targets, and the relevant JS test framework on JS. You write once against `kotlin.test`; the build wires the correct backend. ```kotlin // src/commonTest/kotlin/GreetingTest.kt import kotlin.test.Test import kotlin.test.assertTrue class GreetingTest { @Test fun greetingContainsHello() { val result = Greeting().greet() assertTrue(result.contains("Hello")) } } ``` ## Running them - `./gradlew allTests` runs tests for every target. - `./gradlew jvmTest`, `./gradlew iosSimulatorArm64Test`, etc. run a single target's tests (including the common ones compiled into it). ## When to use platform test source sets If an assertion needs a platform API (e.g. checking a `java.io.File` was written on JVM, or a `NSDate` on iOS), put it in `jvmTest`, `iosTest`, etc. Those depend on their `*Main` counterpart and may use platform APIs and platform-specific test frameworks. ## Coroutines in tests With `kotlinx-coroutines-test` added to `commonTest`, you can use `runTest { ... }` to drive `suspend` functions deterministically in common tests. ## Key takeaways - `commonTest` mirrors `commonMain`, runs once per target. - Use `kotlin.test` annotations/assertions for portability. - The build maps `kotlin.test` to each platform's real runner. - Platform-only assertions go in `jvmTest`/`iosTest`/etc.
- What does `kotlin.test` map to on the JVM?A JUnit backend (JUnit 4 or 5, configurable). The `@Test`/assert calls are routed to JUnit's runner so JVM tests appear as normal JUnit tests.
- Where would a test that asserts on `java.io.File` behavior belong?In `jvmTest`, not `commonTest`, because `java.io.File` is JVM-only and common tests must compile for every target.
saying these in an interview costs you the question
- Using JUnit's `org.junit.Test` directly in `commonTest`
- Thinking common tests run only once total instead of per target
- Not knowing the `kotlin("test")` dependency helper
- Believing platform-specific assertions can live in `commonTest`
- Confusing `commonTest` with an integration-test module