with one click
compose-ui-testing-synchronization
// Compose test synchronization — virtual clock, waitForIdle, waitUntil, autoAdvance, IdlingResource, and v2 migration (StandardTestDispatcher).
// Compose test synchronization — virtual clock, waitForIdle, waitUntil, autoAdvance, IdlingResource, and v2 migration (StandardTestDispatcher).
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | compose-ui-testing-synchronization |
| description | Compose test synchronization — virtual clock, waitForIdle, waitUntil, autoAdvance, IdlingResource, and v2 migration (StandardTestDispatcher). |
| tech_stack | ["compose"] |
| language | ["kotlin"] |
| capability | ["integration-testing","task-scheduler"] |
| version | androidx.compose.ui:ui-test 1.11.0-alpha03+ |
| collected_at | "2026-05-01T00:00:00.000Z" |
Source: https://developer.android.com/develop/ui/compose/testing/synchronization, https://developer.android.com/develop/ui/compose/testing/migrate-v2
Compose tests use a virtual clock — they don't run in real time, so tests
pass as fast as possible. The framework synchronizes automatically: every
assertion/action via ComposeTestRule waits until the UI tree is idle before
proceeding. This skill covers both the default synchronization model and the
manual control APIs needed for timing-sensitive scenarios (animations, async
loads, v2 coroutine dispatching).
LaunchedEffect aren't executing before your assertions — fix with
waitForIdle() or runOnIdle{}autoAdvance and step frame-by-frame with advanceTimeByFrame()IdlingResourcekotlinx.coroutines.test.runTest —
use runComposeUiTest instead// Every action/assertion auto-synchronizes:
composeTestRule.onNodeWithText("Continue").performClick()
composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()
// Recomposition happens during synchronization; state changes alone don't trigger it.
// ❌ Fails in v2 — coroutine hasn't run yet
viewModel.loadData()
assertEquals(Success, viewModel.state.value)
// ✅ waitForIdle advances clock until idle
viewModel.loadData()
composeTestRule.waitForIdle()
assertEquals(Success, viewModel.state.value)
// ✅ runOnIdle executes after idle without inline clock advance
viewModel.loadData()
composeTestRule.runOnIdle { assertEquals(Success, viewModel.state.value) }
| API | Behavior |
|---|---|
mainClock.autoAdvance = false | Disable automatic clock — recompositions paused |
mainClock.advanceTimeByFrame() | Advance exactly 1 frame |
mainClock.advanceTimeBy(ms) | Advance by a duration |
mainClock.advanceTimeUntil(ms){cond} | Advance until Compose-state condition true |
mainClock.scheduler.runCurrent() | Run queued coroutines at current virtual time (v2) |
waitForIdle() | autoAdvance=true → advance clock to idle; autoAdvance=false → wait only for IdlingResources. Always waits for draw/layout. |
waitUntil(ms){cond} | Poll external condition (e.g., data loading, View draw) |
waitUntilAtLeastOneExists(m,ms) | Shorthand — node count ≥ 1 |
waitUntilDoesNotExist(m,ms) | Shorthand — node absent |
waitUntilExactlyOneExists(m,ms) | Shorthand — exactly 1 node |
waitUntilNodeCount(m,n,ms) | Shorthand — count == n |
registerIdlingResource(r) | Register async work tracker |
runOnIdle { … } | Execute block on UI thread after idle |
// Rule factory — find+replace package:
// androidx.compose.ui.test.junit4.createComposeRule
// → androidx.compose.ui.test.junit4.v2.createComposeRule
// Same pattern for: createAndroidComposeRule, createEmptyComposeRule,
// runComposeUiTest, runAndroidComposeUiTest
// runTest + createComposeRule → replace with runComposeUiTest:
@Test
fun testWithCoroutines() = runComposeUiTest {
setContent { /* … */ }
onNodeWithText("Loading...").assertIsDisplayed()
mainClock.advanceTimeBy(1000 + 16 /* frame buffer */)
onNodeWithText("Done!").assertIsDisplayed()
}
MainTestClock doesn't control Android measure/draw passes — those are
external to Compose's virtual clock. Use waitUntil() for conditions
involving View-side state.CountDownLatch instead of waitUntil — the test clock
won't advance and you'll get unexpected behavior.advanceTimeUntil condition must check Compose state only (state that the
virtual clock can affect).UnconfinedTestDispatcher (immediate coroutine
execution) — only AndroidComposeUiTestEnvironment constructor changed to
StandardTestDispatcher by default.runCurrent() vs waitForIdle(): runCurrent drains the queue without
advancing time (good for intermediate states); waitForIdle advances the
clock to stability.waitUntil* helpers.performClick etc. all
auto-synchronize.IdlingResource wrappers for
Espresso's idling registry via compose-ui-testing-hybrid-espresso.