DEV Community

Piotr Chmielowski
Piotr Chmielowski

Posted on

3

Testing Jetpack Compose without emulator or device

How to verify code which uses Jetpack Compose with pure JVM tests running on the developer's PC - without emulator or physical Android device.

Gradle setup

First requirement is to add Jetpack Compose testing artifacts:

testImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
Enter fullscreen mode Exit fullscreen mode

We need also JUnit library:

testImplementation 'junit:junit:4.13.2'
Enter fullscreen mode Exit fullscreen mode

And Robolectric. Robolectric is a test runner which provides Android SDK dependencies for pure JVM tests:

testImplementation 'org.robolectric:robolectric:4.7'
Enter fullscreen mode Exit fullscreen mode

Robolectric also requires the following line in the Gradle configuration:

testOptions {
    unitTests {
        includeAndroidResources = true
    }
}
Enter fullscreen mode Exit fullscreen mode

Here is a final app/build.gradle file:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}
android {
    compileSdk 31
    defaultConfig {
        applicationId "net.chmielowski.testingcompose"
        minSdk 28
        targetSdk 31
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion compose_version
    }
    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }
}
dependencies {
    implementation "androidx.compose.ui:ui:$compose_version"
    implementation "androidx.compose.material:material:$compose_version"

    testImplementation 'junit:junit:4.13.2'
    testImplementation 'org.robolectric:robolectric:4.7'
    testImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
    debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
}
Enter fullscreen mode Exit fullscreen mode

And project level build.gradle file:

buildscript {
    ext {
        compose_version = '1.0.5'
    }
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.0.3"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
Enter fullscreen mode Exit fullscreen mode

Writing test

To use Robolectric, we need to add the following annotation to the test class:

@RunWith(RobolectricTestRunner::class)
Enter fullscreen mode Exit fullscreen mode

To test composables we need to add the following test rule:

@get:Rule
val rule = createComposeRule()
Enter fullscreen mode Exit fullscreen mode

There is also an issue with Robolectric which can be worked around by adding @Config(instrumentedPackages = ["androidx.loader.content"]) annotation to the test class.

Putting it all together, the basic test could look like the following:

package net.chmielowski.testingcompose

import androidx.compose.material.Text
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(instrumentedPackages = ["androidx.loader.content"])
class ExampleUnitTest {

    @get:Rule
    val rule = createComposeRule()

    @Test
    fun basicTestCase() {
        rule.setContent { Text("Hello, World!") }
        rule
            .onNodeWithText("Hello, World!")
            .assertExists()
    }
}
Enter fullscreen mode Exit fullscreen mode

More advanced test

Here is an example of the slightly more advanced test:

package net.chmielowski.testingcompose

import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(instrumentedPackages = ["androidx.loader.content"])
class ExampleUnitTest {

    @get:Rule
    val rule = createComposeRule()

    @Test
    fun basicTestCase() {
        rule.setContent {
            var number by remember { mutableStateOf(0) }
            Text(
                text = number.toString(),
                modifier = Modifier.testTag("Counter"),
            )
            Button(onClick = { number++ }) {
                Text("Increment")
            }
        }
        rule
            .onNodeWithTag("Counter")
            .assertTextEquals("0")
        rule
            .onNodeWithText("Increment")
            .performClick()
        rule
            .onNodeWithTag("Counter")
            .assertTextEquals("1")

    }
}
Enter fullscreen mode Exit fullscreen mode

Sentry mobile image

Mobile Vitals: A first step to Faster Apps

Slow startup times, UI hangs, and frozen frames frustrate users—but they’re also fixable. Mobile Vitals help you measure and understand these performance issues so you can optimize your app’s speed and responsiveness. Learn how to use them to reduce friction and improve user experience.

Read the guide

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay