Hi all, I am trying to migrate to the v2 testing a...
# compose-android
m
Hi all, I am trying to migrate to the v2 testing api, and running into some issues. I'm using a material TimePicker, and in order to select a time in the dial, i have to first click on the nearest 5 minute interval and then click and drag to the actual value i want. This all works, and i can do the calculations and do this block:
Copy code
interaction.performTouchInput {
                down(snapCenter)
                up()
                down(snapCenter)
                moveTo(targetCenter)
                up()
            }

            waitForIdle()
However, in the v2 API the difference is that the
runComposeUiTest
(or even just a normal createComposeRule) has switched to a StandardTestDispatcher from UnconfinedTestDispatcher. I'm able to switch to UnconfinedTestDispatcher, but that's local to one test. I actually have the logic above wrapped up in a test fixture, and if someone uses it without the right dispatcher it won't work. Is there some other alternative other than switching the dispatcher to force the effects to execute at the point where i'm calling waitForIdle
l
Why do you want UnconfinedTestDispatcher? That's not used in production, so by using it you're actually testing a not-in-real-life scenario
m
I don't really WANT the unconfined test dispatcher. What i want is to wait for the drag effect and associated operations to finish. The only way i've found for that so far is to change the dispatcher. That's why i'm asking if there's a better way.
l
I'm not sure what you mean by wait for the drag effect and associated operations to finish - waitForIdle() should wait if the drag is mutating state or something
in some cases if you are launching coroutines, they might not have been dispatched yet, so you might need to use
runCurrent
manually. https://developer.android.com/develop/ui/compose/testing/migrate-v2#manual-synchronization There's some docs here and elsewhere on the page
m
So i think what's happening @Louis Pullen-Freilich [G] is that the initial click is being processed (the first down + up calls). However, the click and drag (down + moveTo + up) is not actually completing in time. This is even with the waitForIdle. So the general behavior i'm seeing is that if i'm trying to click the 58th minute on the dial. It clicks the 55, then clicks and drags from 55 to 58. However, the test fails because the value is changing twice, and the second change is not triggering before the assertion:
Copy code
runComposeUiTest(effectContext = UnconfinedTestDispatcher()) {
            val time = LocalTime.of(17,30)
            var hour by mutableStateOf(time.hour)
            var minute by mutableStateOf(time.minute)

            setContent {
                CLTimePicker(
                    modifier = Modifier.testTag("timePicker"),
                    onHourSelected = { hour = it },
                    onMinuteSelected = { minute = it},
                    initialHour = time.hour,
                    initialMinute = time.minute,
                    mode = CLTimePickerMode.Dial
                )
            }

            val robot = CLTimePickerRobot(this, "timePicker")

            println(robot.interaction.printToString())
            robot
                .assertIs24Hour()
                .selectHour(7)
                .selectMinute(58)

            assertThat(hour).isEqualTo(7)
            assertThat(minute).isEqualTo(58)
        }
so the assertion fails because the value is still 55 and not 58. the "selectMinute" function is where that "performTouchInput" call is happening.
The only thing that's letting this test pass at the moment is changing the dispatcher. I'm not able to get it to wait for the click and drag to finish and trigger the onMinuteSelected. This is likely due to my monitoring the state like this:
Copy code
LaunchedEffect(onMinuteSelected) {
        snapshotFlow { state.minute }.collect {
            onMinuteSelected(it)
        }
    }
So my guess is that the snapshot flow is getting collected and waitForIdle isn't waiting for that LaunchedEffect to be idle.
i've even tried rewriting the test condition to allow more time, instead of a direct assertion:
Copy code
waitUntil(timeoutMillis = 5000) {
                minute == 58
            }
But with a StandardTestDispatcher, it's never delivering the callback.
ok. Seems like that first click is no longer needed, and removing it solves the issue for some reason.