skip to Main Content

I’m currently trying to get testing setup in my first Compose Multiplatform project.

I’ve followed the instructions here:

https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-test.html#writing-and-running-tests-with-compose-multiplatform

So far I can run tests successfuly from the command line using:

./gradlew :composeApp:connectedAndroidTest

When I try to run them from Android Studio (GUI) I get the following error:

java.lang.NullPointerException: Cannot invoke "String.toLowerCase(java.util.Locale)" because "android.os.Build.FINGERPRINT" is null
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.runTest(ComposeUiTest.android.kt:314)
at androidx.compose.ui.test.ComposeUiTest_androidKt.runAndroidComposeUiTest(ComposeUiTest.android.kt:114)
at androidx.compose.ui.test.ComposeUiTest_androidKt.runComposeUiTest(ComposeUiTest.android.kt:61)
at androidx.compose.ui.test.ComposeUiTest_androidKt.runComposeUiTest$default(ComposeUiTest.android.kt:60)
at cm.feature.game.play.TwisterViewModelTest.helloTestingCM(GameViewModelTest.kt:21)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:112)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:40)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:60)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:52)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at jdk.proxy1/jdk.proxy1.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:176)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

Here is my test:

class GameViewModelTest {
@OptIn(ExperimentalTestApi::class)
@Test
fun helloTestingCM() = runComposeUiTest {
    setContent {
        var text by remember { mutableStateOf("Hello") }
        Text(
            text = text,
            modifier = Modifier.testTag("text")
        )
        Button(
            onClick = { text = "Compose" },
            modifier = Modifier.testTag("button")
        ) {
            Text("Click ma")
        }
    }

    // Tests the declared UI with assertions and actions of the Compose Multiplatform testing API
    onNodeWithTag("text").assertTextEquals("Hello")
    onNodeWithTag("button").performClick()
    onNodeWithTag("text").assertTextEquals("Chompose")
}

}

Is there something else I’m missing or has anyone else run into this error? I assume it’s because when I run via command line it’s running on real device and has the missing dependencies.

2

Answers


  1. I was referring to the same test example at https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-test.html#62f6e1cb facing the same error.
    As far as I understand you can run tests for android only from terminal:
    "Currently, you cannot run common Compose Multiplatform tests using android (local) test configurations, so gutter icons in Android Studio, for example, won’t be helpful."

    Login or Signup to reply.
  2. I was able to make the Android (local) test configurations be runnable from Android Studio using Robolectric.

    Add these dependencies to your source set:

    val androidUnitTest by getting {
        dependencies {
            implementation(androidx.compose.ui:ui-test-junit4)
            implementation(libs.androidx.ui.test.manifest)
            implementation(libs.robolectric)
        }
    }
    

    this is my libs.versions.toml:

    [versions]
    compose = "1.6.7"
    robolectric = "4.12"
    
    [libraries]
    androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" }
    androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose" }
    robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
    

    I also needed to add this to my composeApp‘s build.gradle.kts to make resources work:

    android {
        // ...
        compileSdk = libs.versions.android.compileSdk.get().toInt()
        defaultConfig {
            minSdk = libs.versions.android.minSdk.get().toInt()
        }
    
        // add this, NOTE: it's "isIncludeAndroidResources" not "includeAndroidResources"
        testOptions.unitTests.isIncludeAndroidResources = true
    }
    

    You then just need to annotate your test:

    @RunWith(RobolectricTestRunner::class)
    class MyTest {
        // ...
    }
    

    That’s it, it will be runnable with "Android (local)" configuration from Android Studio, and it won’t give errors about "android.os.Build.FINGERPRINT" is null.

    PS: I figured out this solution a long time ago, but I’m posting this answer only now. It could be I missed some important points because it’s not fresh in my head what I did a long time ago to make it work. In case you have questions, ask them in the comments, I’ll try to help!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search