Compose UI Test and Preview Hacks!

Haris Weitani
3 min readMay 17, 2024

--

In real projects, things are often more complex than the examples we find online. Most of the time, when using MVVM as your Android architecture, you need to inject or pass your ViewModel and Navigation controller to your screen.

I will create a simple example to illustrate the pain to create the Preview and UI Test on compose that i feel before i know this hacks.


class ExampleActivity {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupInjection()
setContent {
//Initiate my screen
MyVeryCoolScreen(navController, exampleViewModel)
}
}

}

@Composable
fun MyVeryCoolScreen(
navController: NavController
viewModel: ExampleViewModel,
) {
Text("Very Cool")
}


//Doing preview will be challenging and so the UI Test
@Preview
@Composable
fun MyVeryCoolScreenPreview() {
MyVeryCoolScreen(
navController = rememberNavController(),
viewModel = ???,
)
}

Previews are limited when using ViewModel within a composable. The previews system is not capable of constructing all of the parameters passed to a ViewModel, such as repositories, use cases, managers, or similar. Also, if your ViewModel participates in dependency injection (such as with Hilt), the previews system can't build the whole dependency graph to construct the ViewModel.

Since creating previews with ViewModel is limited, UI tests also face the same limitation. So how do we solve this?

Just kidding :)

We need to create another composable to convert our ViewModel to UIState, we can add “Route”

class ExampleActivity {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupInjection()
setContent {
//Initiate my screen
MyVeryCoolScreenRoute(navController, exampleViewModel)
}
}

}

//New
@Composable
fun MyVeryCoolScreenRoute(
navController: NavController
viewModel: ExampleViewModel,
) {
MyVeryCoolScreen(
viewModel.uiState.value
)
}

//Only pass UI State
@Composable
fun MyVeryCoolScreen(
uiState: MyVeryCoolUiState
) {
Text("Very Cool")
}


@Preview
@Composable
fun MyVeryCoolScreenPreview() {
MyVeryCoolScreen(
MyVeryCoolUiState()
)
}

By doing this we can preview our screen easily by modifying only the UI State. So the UI Test will come easier!

@RunWith(AndroidJUnit4::class)
class MyVeryCoolScreenTest() {

@get:Rule
val composeTestRule = createComposeRule()

private var uiState = mutableStateOf(MyVeryCoolUiState())

@Before
fun setup() {
composeTestRule.setContent {
MyVeryCoolScreen(
uiState = uiState, //Define your UI State and do the test
)
}
}

//Do your test

}

What do you guys think?

if its helpful give it a clap! If you have any questions, I am more than happy to discuss.

if you have any problem about UI test please check this article

https://harisweitani.medium.com/troubleshooting-android-instrument-test-status-issues-in-submodule-projects-85b8f17e64b8

--

--

Haris Weitani
Haris Weitani

Written by Haris Weitani

Hi, i'm software engineer that focused on Android! This page is my learning journey, please feel free to comment and any suggestion will be very welcome!

No responses yet