← Back to Tutorials Chapter 15

Testing & Debugging

Unit Tests

// build.gradle.kts
dependencies {
    testImplementation("junit:junit:4.13.2")
    testImplementation("org.mockito:mockito-core:5.8.0")
    testImplementation("io.mockk:mockk:1.13.8")
}

// ViewModel Unit Test
class UserViewModelTest {
    private lateinit var viewModel: UserViewModel
    private val mockRepo = mock<UserRepository>()

    @Before
    fun setup() {
        viewModel = UserViewModel(mockRepo)
    }

    @Test
    fun `loadUsers should update users LiveData`() = runTest {
        val mockUsers = listOf(User(1, "Alice", "alice@example.com"))
        `when`(mockRepo.getUsers()).thenReturn(mockUsers)

        viewModel.loadUsers()
        val result = viewModel.users.getOrAwaitValue()

        assertEquals(1, result.size)
        assertEquals("Alice", result[0].name)
    }

    @Test
    fun `loadUsers should handle empty response`() = runTest {
        `when`(mockRepo.getUsers()).thenReturn(emptyList())

        viewModel.loadUsers()
        val result = viewModel.users.getOrAwaitValue()

        assertTrue(result.isEmpty())
    }
}

UI Tests with Espresso

// build.gradle.kts
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation("androidx.test.espresso:espresso-contrib:3.5.1")
androidTestImplementation("androidx.test.ext:junit:1.1.5")

@RunWith(AndroidJUnit4::class)
class MainActivityTest {
    @get:Rule
    val activityRule = ActivityScenarioRule(MainActivity::class.java)

    @Test
    fun buttonClick_updatesTextView() {
        // Find the button and click it
        onView(withId(R.id.clickMeButton))
            .perform(click())

        // Check the text view shows expected text
        onView(withId(R.id.greetingText))
            .check(matches(withText("Hello, Android!")))
    }

    @Test
    fun recyclerView_scrolls() {
        onView(withId(R.id.recyclerView))
            .perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
                5, click()
            ))

        onView(withText("Item 6"))
            .check(matches(isDisplayed()))
    }
}

Mockito & MockK

// Mockito (Java-style)
@RunWith(MockitoJUnitRunner::class)
class LoginViewModelTest {
    @Mock lateinit var authRepo: AuthRepository
    lateinit var viewModel: LoginViewModel

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        viewModel = LoginViewModel(authRepo)
    }

    @Test
    fun login_success() {
        `when`(authRepo.login("user", "pass"))
            .thenReturn(Result.success(User("Alice")))

        viewModel.login("user", "pass")
        assertTrue(viewModel.loginResult.value?.isSuccess == true)
    }
}

// MockK (Kotlin-native)
class MockKTest {
    val mockRepo = mockk<AuthRepository>()

    @Test
    fun loginFailsWithWrongPassword() {
        every { mockRepo.login("user", "wrong") }
            .returns(Result.failure(Exception("Invalid credentials")))

        val result = mockRepo.login("user", "wrong")
        assertTrue(result.isFailure)
    }
}

Debugging Tools

// Logging
Log.d("MainActivity", "Button clicked at ${System.currentTimeMillis()}")
Log.e("Network", "Request failed: ${error.message}")
Log.i("Lifecycle", "Activity created")

// Android Profiler (in Android Studio)
// View > Tool Windows > Profiler
// Monitor: CPU, Memory, Network, Energy

// StrictMode (catch accidental disk/network on main thread)
if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
        .detectDiskReads()
        .detectDiskWrites()
        .detectNetwork()
        .penaltyLog()
        .build())
}

// LeakCanary (memory leak detection)
// implementation("com.squareup.leakcanary:leakcanary-android:2.12")

Testing Checklist

Test TypeFrameworkWhat to Test
UnitJUnit + MockKViewModels, Repositories, UseCases
IntegrationJUnit + RoomDatabase, Content Providers
UIEspressoActivities, Fragments, Navigation
End-to-EndCompose UI TestFull user flows
Exercise: Write unit tests for a LoginViewModel using MockK. Mock the AuthRepository and test three scenarios: login succeeds, login fails with wrong password, and login fails with network error. Then write an Espresso UI test that enters credentials, clicks login, and verifies the welcome message appears.