{% setvar book_path %}/reference/androidx/_book.yaml{% endsetvar %} {% include "_shared/_reference-head-tags.html" %}

MainTestClock

public interface MainTestClock


The clock that drives frames, recompositions and launched effects in compose tests.

This clock is ultimately responsible for driving all recompositions, all subscribers to withFrameNanos (all compose animations) and all coroutines launched with LaunchedEffect (for example gesture detection). It is important to realize that if this clock does not tick, recomposition will not happen and animations are frozen. Equally important to realize is that measure, layout and draw passes are not driven by this clock. Instead, they are driven by the event loop of the platform, for example the Choreographer on Android. That means that forwarding this clock will not perform a measure, layout or draw pass, and vice versa, when this clock is paused measure, layout and draw passes can still occur.

Therefore, when setting autoAdvance to false and taking control over this clock, there are several things to realize:

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.unit.dp

@Test
fun testSlideOut() {
    var showBox by mutableStateOf(true)

    composeTestRule.setContent {
        AnimatedVisibility(
            visible = showBox,
            exit = slideOutHorizontally(tween(3000)) { -it }
        ) {
            Box(Modifier.size(100.dp).testTag("box")) {}
        }
    }

    // Take control of the clock
    composeTestRule.mainClock.autoAdvance = false
    composeTestRule.onNodeWithTag("box").assertExists()
    // Start hiding the box
    showBox = false

    // Trigger recomposition
    composeTestRule.mainClock.advanceTimeByFrame()
    // Await layout pass to set up animation
    composeTestRule.waitForIdle()
    // Give animation a start time
    composeTestRule.mainClock.advanceTimeByFrame()

    // Advance clock by first half the animation duration
    composeTestRule.mainClock.advanceTimeBy(1500)
    composeTestRule.onNodeWithTag("box").assertExists()

    // Advance clock by second half the animation duration
    composeTestRule.mainClock.advanceTimeBy(1500)
    composeTestRule.onNodeWithTag("box").assertDoesNotExist()
}
import androidx.compose.material.Text
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.test.onNodeWithText

@Test
fun testControlClock() {
    var toggle by mutableStateOf(false)

    composeTestRule.setContent {
        var count by remember { mutableStateOf(0) }
        DisposableEffect(toggle) {
            count++
            // Apply the change to `count` in the snapshot:
            Snapshot.sendApplyNotifications()
            // Note: we apply the snapshot manually here for illustration purposes. In general
            // we recommended against doing this in production code.
            onDispose {}
        }
        Text("Effect ran $count time(s), toggle is $toggle")
    }

    // Check initial state
    composeTestRule.onNodeWithText("Effect ran 1 time(s), toggle is false").assertExists()
    // Take control of the clock
    composeTestRule.mainClock.autoAdvance = false

    // Change the `toggle` state variable
    toggle = true
    // Apply the change to `toggle` in the snapshot:
    Snapshot.sendApplyNotifications()

    // Recomposition hasn't yet happened:
    composeTestRule.onNodeWithText("Effect ran 1 time(s), toggle is false").assertExists()
    // Forward the clock by 2 frames: 1 for `toggle` and then 1 for `count`
    composeTestRule.mainClock.advanceTimeBy(32)
    // UI now fully reflects the new state
    composeTestRule.onNodeWithText("Effect ran 2 time(s), toggle is true").assertExists()
}

Summary

Public methods

abstract void
advanceTimeBy(long milliseconds, boolean ignoreFrameDuration)

Advances the clock by the given duration.

abstract void

Advances the main clock by the duration of one frame.

abstract void
advanceTimeUntil(
    long timeoutMillis,
    @NonNull Function0<@NonNull Boolean> condition
)

Advances the clock in increments of a single frame until the given condition is satisfied.

abstract boolean

Whether the clock should be advanced by the testing framework while awaiting idleness in order to process any pending work that is driven by this clock.

abstract long

The current time of this clock in milliseconds.

abstract void
setAutoAdvance(boolean autoAdvance)

Whether the clock should be advanced by the testing framework while awaiting idleness in order to process any pending work that is driven by this clock.

Public methods

advanceTimeBy

abstract void advanceTimeBy(long milliseconds, boolean ignoreFrameDuration)

Advances the clock by the given duration. The duration is rounded up to the nearest multiple of the frame duration by default to always produce the same number of frames regardless of the current time of the clock. Use ignoreFrameDuration to disable this behavior. The frame duration is platform dependent. For example, on a JVM (Android and Desktop) it is 16ms. Note that if ignoreFrameDuration is true, the last few milliseconds that are advanced might not be observed by anyone, since most processes are only triggered when a frame is produced.

When using this method to advance the time by several frames in one invocation, measure, layout and draw passes will not happen in between the produced frames. Multiple frames are in general only produced when an animation is running. See MainTestClock for a more in depth explanation of the behavior of your test when controlling the clock.

It is recommended to set autoAdvance to false when using this method, but it is not strictly necessary. When autoAdvance is true, using this method may or may not speed up your test.

Parameters
long milliseconds

The minimal duration to advance the main clock by. Will be rounded up to the nearest frame duration, unless ignoreFrameDuration is true.

boolean ignoreFrameDuration

Whether to avoid rounding up the milliseconds to the nearest multiple of the frame duration. false by default.

advanceTimeByFrame

abstract void advanceTimeByFrame()

Advances the main clock by the duration of one frame.

advanceTimeUntil

abstract void advanceTimeUntil(
    long timeoutMillis,
    @NonNull Function0<@NonNull Boolean> condition
)

Advances the clock in increments of a single frame until the given condition is satisfied.

Note that the condition should only rely on things that are driven by this clock. Measure, layout and draw passes will not happen in between advancements of the clock while waiting for the condition to become true. If your condition relies on the result of measure, layout or draw, use androidx.compose.ui.test.junit4.ComposeTestRule.waitUntil instead.

See MainTestClock for a thorough explanation of what is and what isn't going to happen as a result of a call to advanceTimeBy.

Parameters
long timeoutMillis

The time after which this method throws an exception if the given condition is not satisfied. This is the simulated time not the wall clock or cpu time.

Throws
androidx.compose.ui.test.ComposeTimeoutException

the condition is not satisfied after timeoutMillis.

getAutoAdvance

abstract boolean getAutoAdvance()

Whether the clock should be advanced by the testing framework while awaiting idleness in order to process any pending work that is driven by this clock. This ensures that when the app is androidx.compose.ui.test.junit4.ComposeTestRule.waitForIdle, there are no more pending recompositions or ongoing animations.

If autoAdvance is false, the clock is not advanced while awaiting idleness. Moreover, having pending recompositions or animations is not taken as a sign of pending work (non-idleness) when awaiting idleness, as waiting for a longer time will not make them happen. Note that pending measure, layout or draw passes will still be awaited when awaiting idleness and having autoAdvance set to false, as those passes are not driven by this clock.

By default this is true.

getCurrentTime

abstract long getCurrentTime()

The current time of this clock in milliseconds.

setAutoAdvance

abstract void setAutoAdvance(boolean autoAdvance)

Whether the clock should be advanced by the testing framework while awaiting idleness in order to process any pending work that is driven by this clock. This ensures that when the app is androidx.compose.ui.test.junit4.ComposeTestRule.waitForIdle, there are no more pending recompositions or ongoing animations.

If autoAdvance is false, the clock is not advanced while awaiting idleness. Moreover, having pending recompositions or animations is not taken as a sign of pending work (non-idleness) when awaiting idleness, as waiting for a longer time will not make them happen. Note that pending measure, layout or draw passes will still be awaited when awaiting idleness and having autoAdvance set to false, as those passes are not driven by this clock.

By default this is true.