UI Testing & Integration Testing

📖 Concept

UI testing validates the app's behavior from the user's perspective. At the senior level, you should design a testing strategy that balances speed and coverage.

The testing pyramid:

      /  E2E Tests  \      Few, slow, high confidence
     / UI Tests      \     Moderate count
    / Integration     \    Moderate count
   /  Unit Tests       \   Many, fast, low cost

UI testing frameworks:

  • Compose Testing: Built-in test rules for Compose UIs. Find by semantics, perform actions, assert state.
  • Espresso: For View-based UIs. Find by ID/text, perform actions, check assertions.
  • UI Automator: Tests across apps (system UI, notifications, permissions).
  • Robolectric: Run Android tests on JVM without emulator. Very fast.

Screenshot testing: Compare rendered UI against reference screenshots. Catches visual regressions. Libraries: Paparazzi, Roborazzi, Compose Preview Screenshot Testing.

Integration testing: Tests multiple components working together (ViewModel + Repository + FakeDataSource). Runs on device/emulator. Uses Hilt's @TestInstallIn to replace production modules.

💻 Code Example

codeTap to expand ⛶
1// Compose UI test
2class TaskListScreenTest {
3 @get:Rule
4 val composeRule = createComposeRule()
5
6 @Test
7 fun displaysTasks_whenLoaded() {
8 val tasks = listOf(
9 Task("1", "Buy groceries", false),
10 Task("2", "Write tests", true)
11 )
12
13 composeRule.setContent {
14 TaskListScreen(
15 state = TaskUiState.Success(tasks),
16 onToggle = {},
17 onDelete = {}
18 )
19 }
20
21 // Assert tasks are displayed
22 composeRule.onNodeWithText("Buy groceries").assertIsDisplayed()
23 composeRule.onNodeWithText("Write tests").assertIsDisplayed()
24
25 // Assert completed task has different visual
26 composeRule.onNodeWithText("Write tests")
27 .assertHasClickAction()
28 }
29
30 @Test
31 fun showsLoadingIndicator_whenLoading() {
32 composeRule.setContent {
33 TaskListScreen(
34 state = TaskUiState.Loading,
35 onToggle = {},
36 onDelete = {}
37 )
38 }
39
40 composeRule.onNodeWithContentDescription("Loading")
41 .assertIsDisplayed()
42 }
43
44 @Test
45 fun callsOnToggle_whenTaskClicked() {
46 var toggledId: String? = null
47
48 composeRule.setContent {
49 TaskListScreen(
50 state = TaskUiState.Success(listOf(Task("1", "Test", false))),
51 onToggle = { toggledId = it },
52 onDelete = {}
53 )
54 }
55
56 composeRule.onNodeWithText("Test").performClick()
57 assertEquals("1", toggledId)
58 }
59}
60
61// Integration test with Hilt
62@HiltAndroidTest
63@RunWith(AndroidJUnit4::class)
64class TaskFeatureTest {
65 @get:Rule(order = 0)
66 val hiltRule = HiltAndroidRule(this)
67
68 @get:Rule(order = 1)
69 val composeRule = createAndroidComposeRule<MainActivity>()
70
71 @BindValue // Replace real repository with fake
72 val repository: TaskRepository = FakeTaskRepository()
73
74 @Before
75 fun setup() {
76 hiltRule.inject()
77 }
78
79 @Test
80 fun createAndCompleteTask_endToEnd() {
81 // Add a task
82 composeRule.onNodeWithContentDescription("Add Task").performClick()
83 composeRule.onNodeWithTag("task_input").performTextInput("New task")
84 composeRule.onNodeWithText("Save").performClick()
85
86 // Verify it appears
87 composeRule.onNodeWithText("New task").assertIsDisplayed()
88
89 // Toggle complete
90 composeRule.onNodeWithText("New task").performClick()
91
92 // Verify completion state
93 composeRule.onNodeWithText("New task")
94 .assertExists() // Still visible but marked complete
95 }
96}

🏋️ Practice Exercise

Practice:

  1. Write Compose UI tests for a login screen (email validation, button state, error display)
  2. Create a screenshot test using Paparazzi for a card component
  3. Write an integration test that uses Hilt's @BindValue to inject fakes
  4. Test navigation between two screens using the Navigation test artifact
  5. Set up Robolectric for fast JVM-based UI tests

⚠️ Common Mistakes

  • Testing UI implementation details (view IDs) instead of user-visible behavior (text, content descriptions)

  • Not adding testTags/semantics to Compose components — makes testing impossible without them

  • Running all tests as instrumented tests — use Robolectric for fast feedback loop

  • Not testing error states — only testing the happy path misses the most common UX issues

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for UI Testing & Integration Testing. Login to unlock this feature.