Kotlin Help

Lincheck in Kotlin Multiplatform projects

Lincheck can be used with Kotlin Multiplatform to test the code targeting the JVM. This article will guide you through setting up Lincheck tests in a Kotlin Multiplatform project.

You will:

  • Set up your project to run Lincheck tests for targets using the JVM.

  • Create classes for counter data structures shared between all targets.

  • Create tests for the counters shared between all targets and tests specific to the JVM.

  • Run the tests.

Set up a project

  1. Create a new Kotlin Multiplatform project in IntelliJ IDEA. When creating a project, make sure that:

    • The Include tests checkbox is checked.

    • Your project has a JVM-based (a server, desktop, or Android) target. You can verify that your code compiles for the JVM by checking the shared/build.gradle.kts or core/build.gradle.kts file:

      kotlin { // ... jvm() // ... // If you have an Android target, the use of JVM is // configured in the `androidLibrary` section androidLibrary { // ... compilerOptions { jvmTarget = JvmTarget.JVM_11 } // ... } }
  2. Set up the tests folder and Lincheck dependency according to the target platforms in your project:

Desktop/Android + iOS

The directory for the platform-specific tests is created by the Kotlin Multiplatform plugin automatically. For example, for a desktop target it is shared/src/jvmTest/kotlin.

Add a Lincheck dependency to the platform-specific test source set and a kotlin.test dependency to the commonTest source set:

// shared/build.gradle.kts kotlin { sourceSets { commonTest.dependencies { implementation(libs.kotlin.test) } jvmTest { implementation("org.jetbrains.lincheck:lincheck:3.6") } } }
// shared/build.gradle.kts kotlin { sourceSets { commonTest.dependencies { implementation(libs.kotlin.test) } getByName("androidHostTest").dependencies { implementation("org.jetbrains.lincheck:lincheck:3.6") } } }

Server + iOS, Server + Desktop, Server + Desktop + iOS

  1. Create a core/src/jvmTest/kotlin directory. Gradle will run the tests in this directory for all platforms that use the JVM.

    Projects with a desktop target also have a shared/src/jvmTest/kotlin directory which you can use instead. However, it is recommended to place the code relevant to both the server and desktop/iOS targets in the core directory.

    If you have a desktop target and create a core/src/jvmTest/kotlin directory, delete the shared/src/jvmTest/kotlin directory.

  2. Add a Lincheck dependency to the jvmTest source set and a kotlin.test dependency to the commonTest source set:

    // core/build.gradle.kts kotlin { // ... sourceSets { // ... commonTest.dependencies { implementation(libs.kotlin.test) } jvmTest.dependencies { implementation("org.jetbrains.lincheck:lincheck:3.6") } } }

Create shared classes

Create classes implementing counter data structures that are shared between all targets:

  1. Create an UnsafeCounter.kt file in the core/src/commonMain or shared/src/commonMain directory:

    class UnsafeCounter { private var value: Int = 0 fun inc() = value++ fun get(): Int = value }
  2. Create a SafeCounter.kt file in the same directory:

    import kotlin.concurrent.atomics.AtomicInt import kotlin.concurrent.atomics.ExperimentalAtomicApi @OptIn(ExperimentalAtomicApi::class) class SafeCounter { private var value = AtomicInt(0) fun inc() = value.addAndFetch(1) fun get(): Int = value.load() }

Write shared tests

Write tests that are run for all targets:

  1. Create an UnsafeCounterTest.kt file in the core/src/commonTest or shared/src/commonTest directory:

    import kotlin.test.Test import kotlin.test.assertEquals class UnsafeCounterTest { @Test fun testIncrement() { val counter = UnsafeCounter() assertEquals(0, counter.get(), "Initial value should be 0") counter.inc() assertEquals(1, counter.get(), "Value after one increment should be 1") } }
  2. Create a SafeCounterTest.kt file in the same directory:

    import kotlin.test.Test import kotlin.test.assertEquals class SafeCounterTest { @Test fun testIncrement() { val counter = SafeCounter() assertEquals(0, counter.get(), "Initial value should be 0") counter.inc() assertEquals(1, counter.get(), "Value after one increment should be 1") } }

Write JVM-specific tests

Write tests that are only run for platforms targeting the JVM:

  1. Create an UnsafeCounterConcurrentTest.kt file in the test directory for JVM-specific tests:

    • Android + iOS – shared/src/androidHostTest/kotlin

    • Desktop + iOS – shared/src/jvmTest/kotlin

    • Any project with a server target – core/src/jvmTest/kotlin

    import org.jetbrains.lincheck.datastructures.ModelCheckingOptions import org.jetbrains.lincheck.datastructures.Operation import kotlin.test.Test class UnsafeCounterConcurrentTest { private val c = UnsafeCounter() @Operation fun inc() = c.inc() @Operation fun get() = c.get() @Test fun modelCheckingTest() { ModelCheckingOptions().check(this::class) } }
  2. Create a SafeCounterConcurrentTest.kt file in the same directory:

    import org.jetbrains.lincheck.datastructures.ModelCheckingOptions import org.jetbrains.lincheck.datastructures.Operation import kotlin.test.Test class SafeCounterConcurrentTest { private val c = SafeCounter() @Operation fun inc() = c.inc() @Operation fun get() = c.get() @Test fun modelCheckingTest() { ModelCheckingOptions().check(this::class) } }

Run the tests

At this point, your project should have the shared implementation of the counter data structures, the shared tests, and the JVM-specific tests for counters. The directory structure of your project should look like this:

A screenshot of the project file structure for the Android and iOS platforms.
A screenshot of the project file structure for the server and iOS platforms.
A screenshot of the project file structure for the desktop and iOS platforms.
A screenshot of the project file structure for the server, desktop, and iOS platforms.

You can run individual tests from the context menu, using the shortcut, or by running a Gradle task. If you run the allTests Gradle task, every test in your project will be executed with the corresponding test runner:

A screenshot of the Gradle plugin in IntelliJ IDEA with the Tasks | Verification | allTests task highlighted.

For example, the JVM-specific tests are executed with the JVM test runner:

A screenshot of the test execution report. The Lincheck tests are marked with a jvm tag.

Support for platforms other than the JVM

Lincheck is unlikely to support other platforms in the future.

Lincheck relies heavily on JVM for bytecode manipulation, which makes model checking challenging to implement on other platforms. Moreover, using model checking for multiple platforms at the same time provides little value because Lincheck tests the code in a deterministic sandbox environment, and the results of the tests will be the same regardless of the platform.

See also

05 August 2026