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}\nRun Code Online (Sandbox Code Playgroud)\n我们面临的问题是,有时测试会失败并出现以下错误:
\nandroidx.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)\nRun 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"\nRun Code Online (Sandbox Code Playgroud)\n我们的实现有什么问题吗?
\n编辑
\n经过多次尝试,我们发现了这个问题的原因,我们有两个可变状态:
\nval 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})\nRun Code Online (Sandbox Code Playgroud)\n禁用此代码后,测试可以正常运行,闲置资源也将闲置。也许我们这段代码的实现有问题?
\n我有类似的问题。我的问题是如何检索 CoroutineScope 进行测试:
@get:Rule()
var coroutineRule = MainDispatcherRule() // this caused the issue
Run Code Online (Sandbox Code Playgroud)
我MainDispatcherRule从Test 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)