AndroidComposeRule 测试问题空闲资源超时:可能是由于 compose 正忙

Vik*_*kov 8 android android-jetpack-compose

我们似乎遇到了 AndroidComposeRule 的问题,我们使用 waitUntil {} 函数进行了一个简单的测试:

\n
@RunWith(AndroidJUnit4::class)\nclass IdenfyFaceReauthenticationFlowTests {\n\n  @get:Rule\n  val composeTestRule = createAndroidComposeRule<FaceReauthenticationActivity>()\n\n  private lateinit var faceReauthenticationActivity: FaceReauthenticationActivity\n\n  @Before\n  fun setUp() {\n    faceReauthenticationActivity = composeTestRule.activity\n    faceReauthenticationActivity.idenfyMainViewModel.idenfyInternalSettings.isInitialViewClosed = false\n  }\n\n  @Test\n  fun test1() {\n    composeTestRule.waitUntil(8000) {\n      composeTestRule.onAllNodesWithText(faceReauthenticationActivity.getString(R.string.idenfy_camera_onboarding_view_continue_button_title_v2)).fetchSemanticsNodes(false).isNotEmpty()\n    }\n    composeTestRule.onNodeWithText(faceReauthenticationActivity.getString(R.string.idenfy_camera_onboarding_view_continue_button_title_v2)).performClick()\n\n    Thread.sleep(6000)\n\n    //Face Reauthentication Results View V2\n    IdenfyView().waitForView(ViewMatchers.withId(R.id.idenfy_button_face_reauthentication_results_continue)).perform(\n      ViewActions.click())\n\n    Thread.sleep(1000)\n\n    val activityResult = composeTestRule.activityRule.scenario.result!!\n    activityResult.resultData.setExtrasClassLoader(this::class.java.classLoader)\n    val faceReauthenticationResult: FaceReauthenticationResult =\n      activityResult.resultData.getParcelableExtra(IdenfyController.IDENFY_FACE_REAUTHENTICATION_RESULT)!!\n\n    assert(faceReauthenticationResult.faceReauthenticationStatus == FaceReauthenticationStatus.SUCCESS)\n  }\n\n  @After\n  fun tearDown() {\n    composeTestRule.activityRule.scenario.close()\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我们面临的问题是,有时测试会失败并出现以下错误:

\n
androidx.compose.ui.test.junit4.android.ComposeNotIdleException: Idling resource timed out: possibly due to compose being busy.\nIdlingResourceRegistry has the following idling resources registered:\n- [busy] androidx.compose.ui.test.junit4.android.ComposeIdlingResource@df5cec9 \nAll registered idling resources: Compose-Espresso link\n    at androidx.compose.ui.test.junit4.android.EspressoLink_androidKt.rethrowWithMoreInfo(EspressoLink.android.kt:135)\n    at androidx.compose.ui.test.junit4.android.EspressoLink_androidKt.runEspressoOnIdle(EspressoLink.android.kt:109)\n    at androidx.compose.ui.test.junit4.android.EspressoLink.runUntilIdle(EspressoLink.android.kt:78)\n    at androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitForIdle(AndroidComposeTestRule.android.kt:289)\n    at androidx.compose.ui.test.junit4.AndroidComposeTestRule.access$waitForIdle(AndroidComposeTestRule.android.kt:155)\n    at androidx.compose.ui.test.junit4.AndroidComposeTestRule$AndroidTestOwner.getRoots(AndroidComposeTestRule.android.kt:441)\n    at androidx.compose.ui.test.TestContext.getAllSemanticsNodes$ui_test_release(TestOwner.kt:95)\n    at androidx.compose.ui.test.SemanticsNodeInteractionCollection.fetchSemanticsNodes(SemanticsNodeInteraction.kt:234)\n    at androidx.compose.ui.test.SemanticsNodeInteractionCollection.fetchSemanticsNodes$default(SemanticsNodeInteraction.kt:227)\n    at com.idenfy.idenfySdk.flowtests.IdenfyFaceReauthenticationFlowTests$reauthenticationOldStatusReturned_failedStatusReturned$1.invoke(IdenfyFaceReauthenticationFlowTests.kt:145)\n    at com.idenfy.idenfySdk.flowtests.IdenfyFaceReauthenticationFlowTests$reauthenticationOldStatusReturned_failedStatusReturned$1.invoke(IdenfyFaceReauthenticationFlowTests.kt:144)\n    at androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntil(AndroidComposeTestRule.android.kt:317)\n    at com.idenfy.idenfySdk.flowtests.IdenfyFaceReauthenticationFlowTests.reauthenticationOldStatusReturned_failedStatusReturned(IdenfyFaceReauthenticationFlowTests.kt:144)\n    at java.lang.reflect.Method.invoke(Native Method)\n    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)\n    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)\n    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)\n    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)\n    at androidx.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)\n    at androidx.test.internal.runner.junit4.statement.RunAfters.evaluate(RunAfters.java:61)\n    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)\n    at androidx.compose.ui.test.junit4.AndroidComposeTestRule$AndroidComposeStatement.evaluateInner(AndroidComposeTestRule.android.kt:357)\n    at androidx.compose.ui.test.junit4.AndroidComposeTestRule$AndroidComposeStatement.evaluate(AndroidComposeTestRule.android.kt:346)\n    at androidx.compose.ui.test.junit4.android.EspressoLink$getStatementFor$1.evaluate(EspressoLink.android.kt:63)\n    at androidx.compose.ui.test.junit4.IdlingResourceRegistry$getStatementFor$1.evaluate(IdlingResourceRegistry.jvm.kt:160)\n    at androidx.compose.ui.test.junit4.android.ComposeRootRegistry$getStatementFor$1.evaluate(ComposeRootRegistry.android.kt:150)\n    at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)\n    at org.junit.rules.RunRules.evaluate(RunRules.java:20)\n    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)\n    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)\n    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)\n    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)\n    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)\n    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)\n    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)\n    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)\n    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)\n    at androidx.test.ext.junit.runners.AndroidJUnit4.run(AndroidJUnit4.java:154)\n    at org.junit.runners.Suite.runChild(Suite.java:128)\n    at org.junit.runners.Suite.runChild(Suite.java:27)\n    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)\n    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)\n    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)\n    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)\n    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)\n    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)\n    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)\n    at org.junit.runner.JUnitCore.run(JUnitCore.java:115)\n    at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)\n    at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:395)\n    at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2252)\nCaused by: androidx.test.espresso.IdlingResourceTimeoutException: Wait for [Compose-Espresso link] to become idle timed out\n    at androidx.test.espresso.IdlingPolicy.handleTimeout(IdlingPolicy.java:16)\n    at androidx.test.espresso.base.UiControllerImpl$5.resourcesHaveTimedOut(UiControllerImpl.java:4)\n    at androidx.test.espresso.base.IdlingResourceRegistry$Dispatcher.handleTimeout(IdlingResourceRegistry.java:44)\n    at androidx.test.espresso.base.IdlingResourceRegistry$Dispatcher.handleMessage(IdlingResourceRegistry.java:12)\n    at android.os.Handler.dispatchMessage(Handler.java:108)\n    at androidx.test.espresso.base.Interrogator.loopAndInterrogate(Interrogator.java:53)\n    at androidx.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:155)\n    at androidx.test.espresso.base.UiControllerImpl.loopMainThreadUntilIdle(UiControllerImpl.java:129)\n    at androidx.test.espresso.Espresso$1.run(Espresso.java:2)\n    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:458)\n    at java.util.concurrent.FutureTask.run(FutureTask.java:266)\n    at android.os.Handler.handleCallback(Handler.java:907)\n    at android.os.Handler.dispatchMessage(Handler.java:105)\n    at android.os.Looper.loop(Looper.java:216)\n    at android.app.ActivityThread.main(ActivityThread.java:7625)\n    at java.lang.reflect.Method.invoke(Native Method)\n    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524)\n    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:987)\n
Run Code Online (Sandbox Code Playgroud)\n

测试似乎陷入了这一行(可组合项绝对可见):

\n
   composeTestRule.waitUntil(8000) {\n      composeTestRule.onAllNodesWithText(faceReauthenticationActivity.getString(R.string.idenfy_camera_onboarding_view_continue_button_title_v2)).fetchSemanticsNodes(false).isNotEmpty()\n    }\n\nOur dependencies (With 1.0.5 compose version):\n//Compose\nimplementation \xe2\x80\x9candroidx.compose.ui:ui:$compose_version\xe2\x80\x9d\nimplementation \xe2\x80\x9candroidx.compose.material:material:$compose_version\xe2\x80\x9d\nimplementation \xe2\x80\x9candroidx.compose.ui:ui-tooling-preview:$compose_version\xe2\x80\x9d\nimplementation \xe2\x80\x98androidx.lifecycle:lifecycle-runtime-ktx:2.4.0\xe2\x80\x99\nimplementation \xe2\x80\x98androidx.activity:activity-compose:1.4.0\xe2\x80\x99\n//Compose testing\ndebugImplementation \xe2\x80\x9candroidx.compose.ui:ui-test-manifest:1.0.5"\n
Run Code Online (Sandbox Code Playgroud)\n

我们的实现有什么问题吗?

\n

编辑

\n

经过多次尝试,我们发现了这个问题的原因,我们有两个可变状态:

\n
val currentInstructionDescription: MutableState<String> = remember {\n  mutableStateOf("")\n}\n\nval progress = remember {\n  mutableStateOf(0.0f)\n}\n\nThese two states are changing all the time in the view (every second), one of them changes the progress of a progress bar, another updates a text:\n\nLaunchedEffect(key1 = Unit, block = {\n  val timer = (0..Int.MAX_VALUE)\n    .asSequence()\n    .asFlow()\n    .onEach { delay(1000) }\n\n  timer.collect {\n    progress.value = progressValueInFloat // increasing value\n    currentInstructionDescription.value = "Custom String"\n  }\n})\n
Run Code Online (Sandbox Code Playgroud)\n

禁用此代码后,测试可以正常运行,闲置资源也将闲置。也许我们这段代码的实现有问题?

\n

leo*_*leo 2

我有类似的问题。我的问题是如何检索 CoroutineScope 进行测试:

@get:Rule()
var coroutineRule = MainDispatcherRule() // this caused the issue
Run Code Online (Sandbox Code Playgroud)

MainDispatcherRuleTest Kotlin coroutines on Android获得了实现,但它导致了问题:

// Reusable JUnit4 TestRule to override the Main dispatcher
class MainDispatcherRule(
    val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
) : TestWatcher() {
    override fun starting(description: Description) {
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description) {
        Dispatchers.resetMain()
    }
}
Run Code Online (Sandbox Code Playgroud)

索恩:

按照这篇文章中coroutineScope所示的方式获取:

val coroutineScope = CoroutineScope(Dispatchers.Unconfined)
Run Code Online (Sandbox Code Playgroud)