Compose Multiplatform (CMP) enables building UI for multiple platforms using Jetpack Compose. Fortunately, CMP also supports writing and running UI tests in the common code, making testing more efficient across platforms. In this post, we’ll explore how to test CMP applications using compose.uiTest
and run them on Android, Desktop, and iOS.
1. Setting Up Common UI Testing
CMP provides compose.uiTest
, allowing UI tests to be written in the shared module without platform-specific dependencies. This means you can write once and test everywhere.
Updating Build Configuration
To enable testing, update your build.gradle.kts
file in the shared module:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| kotlin {
androidTarget {
instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test)
}
sourceSets {
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.uiTest) // Common UI testing library
}
}
val desktopTest by getting {
dependencies {
implementation(compose.desktop.currentOs)
}
}
}
}
|
Declaring Android UI Test Dependencies
At the root level of your build.gradle.kts
file, add the necessary Android test dependencies:
1
2
3
4
| dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4-android:1.5.4")
debugImplementation("androidx.compose.ui:ui-test-manifest:1.5.4")
}
|
Setting Up Android-Specific Test Configuration
For Android instrumentation tests, add the following to your build.gradle.kts
:
1
2
3
4
5
| android {
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}
|
2. Implementing a Simple Counter UI
Let’s create a CounterViewModel that will be tested:
1
2
3
4
5
6
7
8
9
10
11
12
| import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class CounterViewModel {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count.asStateFlow()
fun increment() {
_count.value += 1
}
}
|
Now, let’s create the Composable UI that interacts with this ViewModel:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
@Composable
fun CounterScreen(viewModel: CounterViewModel) {
val count by viewModel.count.collectAsState()
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = count.toString(),
fontSize = 24.sp,
modifier = Modifier.testTag("counterText")
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = { viewModel.increment() },
modifier = Modifier.testTag("incrementButton")
) {
Text("Increment")
}
}
}
|
3. Writing a Common UI Test in CMP
Now, let’s write a UI test in commonTest
to validate that clicking the button increments the counter:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| import androidx.compose.ui.test.*
import kotlin.test.Test
import kotlin.test.assertEquals
class CounterScreenTest {
@OptIn(ExperimentalTestApi::class)
@Test
fun testButtonIncrementsCounter() = runComposeUiTest {
val viewModel = CounterViewModel()
setContent {
CounterScreen(viewModel = viewModel)
}
onNodeWithTag("counterText").assertTextEquals("0")
onNodeWithTag("incrementButton").performClick()
onNodeWithTag("counterText").assertTextEquals("1")
}
}
|
Now that we have our test written in common code, let’s execute it on Android, Desktop, and iOS.
Run Tests on Android
For Android instrumented tests, run:
1
| ./gradlew connectedAndroidTest
|
Run Tests on Desktop
Run Tests on iOS
For iOS simulator tests, run:
1
| ./gradlew :composeApp:iosSimulatorArm64Test
|
5. Why Test CMP from Common Code?
✅ Write once, test everywhere: No need to duplicate tests across platforms.
✅ Consistent behavior across platforms: Ensures UI elements function the same way.
✅ Easier maintenance: Single test suite covering all targets.
Conclusion
With Compose Multiplatform UI testing, we can validate UI behavior from shared code without requiring platform-specific test implementations. The compose.uiTest
library allows us to test UI interactions like text verification and button clicks, ensuring UI consistency across Android, iOS, and Desktop.