Chapter 13 - Testing the Composable UI ArtListScreen
@RunWith(AndroidJUnit4::class)
class ArtListScreenTest {
@get:Rule
val composableRule = createComposeRule()
//fake repository
private val fakeRepo = object : ArtRepositoryInterface {
override fun getArt(): Flow<List<Art>> = flowOf(
listOf(
Art(id = 1, name = "Starry Horse", artistName = "Viru Pandey", year = "1904", imageUrl = "url1"),
Art(id = 2, name = "Mano ya na mano", artistName = "Satish Shah", year = "1998", imageUrl = "url2")
)
)
override fun insertArt(art: Art) {}
override fun deleteArt(art: Art) {}
override fun searchImage(userQuery: String): Flow<Resource<List<ImageResult>>> =
flow { emit(Resource.Success(emptyList())) }
}
private val getArtUseCase = GetArtUseCase(fakeRepo)
private val viewModel = ArtViewModel(getArtUseCase)
private val lists = listOf(Art(1, "Starry Horse", "Viru Pandey", "1904", "url1"),
Art(2, "Mano ya na mano", "Satish Shah", "1998", "url2"))
// 1. Normal data case
@Test
fun artListScreen_displaysArtNamesFromViewModel() {
composableRule.setContent {
ArtListScreen(
artList = lists
)
}
composableRule.onNodeWithText("Starry Horse").assertIsDisplayed()
composableRule.onNodeWithText("Mano ya na mano").assertIsDisplayed()
}
// 2. Loading state test
@Test
fun artListScreen_showsLoadingIndicatorWhenLoading() {
composableRule.setContent {
ArtListScreen(
artList = emptyList(),
isLoading = true
)
}
composableRule.onNodeWithTag("Progress").assertExists()
}
// 3. Error state test
@Test
fun artListScreen_displaysErrorMessageWhenErrorOccurs() {
// Option 1: Pass error directly (simplest)
composableRule.setContent {
ArtListScreen(
artList = emptyList(),
errorMessage = "Something went wrong"
)
}
composableRule.onNodeWithText("Something went wrong").assertIsDisplayed()
}
}
The above code is an instrumentation test and will run on an android device or emulator.
We test the following
The fake data appears on screen
Loading & error states behave as expected.
Test 1 : artListScreen_displaysArtListCorrectly
Purpose
Verifies that the UI displays the list of artworks correctly when provided with sample data.
Setup
Creates a list of two Art objects (Starry Horse and Mano ya na mano).
Renders the ArtListScreen composable with this list.
Assertions
Ensures that both artwork titles are visible on the screen using onNodeWithText().assertIsDisplayed().
Confirms that LazyColumn correctly renders multiple items.
Summary
This is a sanity UI test — checks that Compose correctly binds data to the visible layout.
It doesn’t depend on any repository or ViewModel — pure composable test.
Test 2 : artListScreen_showsLoadingIndicatorWhenLoading
Purpose
Validates that the progress indicator (loading spinner) is displayed when the screen is in a loading state.
Setup
Passes isLoading = true to ArtListScreen.
Uses a Modifier.testTag("Progress") for the loading indicator in the composable.
Assertions
- Checks that a node with tag "Progress" exists on the screen (assertExists()).
Summary
Helps verify that UI reacts properly to loading state changes.
In the real app, this state will be controlled by viewModel.isLoading.
This is important for user feedback during data fetching.
Test 3 : artListScreen_displaysErrorMessageWhenErrorOccurs
Purpose
Checks that an error message is displayed if something goes wrong.
Setup
Renders ArtListScreen with errorMessage = "Something went wrong".
Passes an empty artList (since we’re only testing error display).
Assertions
- Verifies that the error text is visible on the screen using onNodeWithText("Something went wrong").assertIsDisplayed().
Summary
Simulates the scenario where a Room query fails.
Confirms that the user is not left with a blank screen but sees a clear error message.
This test ensures that your UI reacts correctly to error state updates from the ViewModel.
FINAL SUMMARY
These three tests together verify UI reactivity — the most important principle of Jetpack Compose.
They prove that when the ViewModel updates the StateFlow (data, loading, error), the UI layer reflects those changes instantly and correctly.
The flowchart sums up the work that we have done so far.