You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Android Studio中运行Compose Multiplatform测试?命令行已可用

Compose Multiplatform测试在Android Studio GUI运行时出现NullPointerException

问题描述

为首个Compose Multiplatform项目配置测试环境,遵循官方文档操作后,命令行执行./gradlew :composeApp:connectedAndroidTest可成功运行测试,但在Android Studio(GUI)中运行时抛出空指针错误:

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)

测试代码示例:

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")
        }
    }

    onNodeWithTag("text").assertTextEquals("Hello")
    onNodeWithTag("button").performClick()
    onNodeWithTag("text").assertTextEquals("Chompose")
}
}

问题原因

核心原因是Android Studio将Compose UI测试识别成了本地JVM单元测试,而非Android设备/模拟器上运行的Instrumented测试。本地单元测试在无Android系统环境的JVM中执行,导致android.os.Build相关字段(如FINGERPRINT)为null,进而触发空指针异常。而命令行的connectedAndroidTest任务默认针对设备/模拟器运行Instrumented测试,因此能正常执行。

解决方案

1. 确认测试文件目录位置

Compose UI测试属于Instrumented测试,必须放在src/androidTest/kotlin/目录下,而非src/test/kotlin/(后者用于本地单元测试)。如果文件位置错误,Android Studio会默认按本地单元测试运行。

2. 选择正确的运行方式

  • 右键测试方法或类,在弹出菜单中选择**"Run '<测试方法名>' on Android Device"**(或类似选项,确保目标是设备/模拟器)。
  • 若默认菜单选项不正确,可手动创建运行配置:
    1. 打开Run > Edit Configurations
    2. 点击+号,选择Android Instrumented Tests
    3. 指定测试所属模块,选择目标测试类/方法,保存后运行。

3. 验证依赖配置

确保模块的build.gradle.kts中,Compose测试相关依赖添加在androidTestImplementation和debugImplementation下:

android {
    // ...其他配置
}

dependencies {
    // Instrumented测试依赖
    androidTestImplementation("androidx.compose.ui:ui-test-junit4:${composeVersion}")
    debugImplementation("androidx.compose.ui:ui-test-manifest:${composeVersion}")
    // ...其他依赖
}

注意不要将这些依赖误加到testImplementation(本地单元测试依赖块)中。

额外提示

测试代码中的断言存在一处笔误:assertTextEquals("Chompose")应改为assertTextEquals("Compose"),否则测试最终会断言失败,不过这和当前空指针错误无关。

内容的提问来源于stack exchange,提问作者Paul Frank Allan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 17:37:23