In modern Android development, teams increasingly rely on automated UI testing as their apps grow more complex. However, some UI components that are easy to build can be surprisingly difficult to test. Somkiat Khitwongwattana, Staff Software Engineer at LINE MAN Wongnai, recently shared his insights on one of the most common flaky test culprits: the snackbar.
The standard way to implement a snackbar in Jetpack Compose involves using a scaffold, a SnackbarHost, and a SnackbarHostState. While this works perfectly for users, it often fails in automated CI/CD environments.
The problem is one of timing. Consider a coupon redemption flow:
In a UI test, the verification for the second message often fails because the first message hasn't disappeared yet. Developers often resort to removing the verification just to make the test pass, but this compromises quality.
To eliminate flakiness, use a two-part architecture that gives tests manual control over snackbar behavior: a controller to dismiss messages and a container to manage their duration.
Purpose: A registry to store SnackbarHostState instances created during app runtime so they can be dismissed programmatically by the test.
Key Functions:
registerSnackbarHostState: Adds a state to the tracking set.
dismissCurrentSnackbar: Manually triggers dismiss() on any active snackbar.
clearAllSnackbarHostStates: Cleans up after tests to prevent memory leaks.
To keep production code clean, this provider uses Java Reflection to check if the test controller is available.
If the app is running in a test environment, it automatically registers the SnackbarHostState with the controller.
It uses CompositionLocalProvider to make the state easily accessible throughout the UI tree.
This component solves the duration problem. In production, snackbars use short or long durations. When isRunningUiTest() returns true, the container overrides the duration to Indefinite. This ensures the snackbar stays on screen exactly as long as the test needs to verify it, never disappearing prematurely.
With this architecture in place, your UI tests become deterministic and easy to read. Instead of hoping the timing is right, your test code follows a clear verify-and-dismiss pattern:
Kotlin
@Test |
While this method is powerful, there are two critical points to bear in mind:
1. Don't leak test code: Ensure the SnackbarUiTestController is strictly in your androidTest folder. If it accidentally ships in production, your snackbars might stay on screen indefinitely for real users.
2. Complexity: In complex apps, snackbars from other components might appear during tests, interfering with your assertions. Always use a @After tear-down method to clear all host states between tests.
By enforcing an indefinite duration during tests and using a centralized controller to dismiss messages, you can transform flaky snackbar tests into a stable part of your testing suite. This approach moves beyond hoping the UI stays still and gives developers the tools to command it.