Performance, memory leaks & testing
A memory leak is a no-longer-needed object "hiding" from GC: something still references it. Android's classic leak sources:
- An Activity/Context/View in a static field
- Fragment bindings not cleared in onDestroyView
- Unregistered listeners/receivers/callbacks
- Anonymous inner classes (Handler, Runnable) holding the outer class
- Activity contexts handed to singletons
Tools: LeakCanary (automatic detection in debug), the Memory Profiler (heap dump analysis). Symptoms: slowly climbing memory, OutOfMemoryError, jank from GC pauses.
The Android test pyramid:
- Unit tests (many, fast) — ViewModel, use case, repository logic; JUnit + fakes/MockK;
runTest+TestDispatcherfor coroutines; Turbine for Flows. - Integration — Room DAOs (in-memory DB), the API layer (MockWebServer).
- UI tests (few, slow) — Espresso (Views), Compose Testing (
composeTestRule); for critical flows (login, transfers).
DI pays off here: constructor injection lets everything be swapped for fakes.
@OptIn(ExperimentalCoroutinesApi::class)
class TransferViewModelTest {
private val dispatcher = StandardTestDispatcher()
@Before fun setup() { Dispatchers.setMain(dispatcher) }
@After fun tearDown() { Dispatchers.resetMain() }
@Test
fun `declined transfer surfaces reason`() = runTest {
val repo = FakeTransferRepository(
result = TransferResult.Declined("Günlük limit aşılıb")
)
val vm = TransferViewModel(repo)
vm.uiState.test { // Turbine
vm.submit(amount = "5000.00", to = "AZ45...")
advanceUntilIdle()
val state = expectMostRecentItem()
assertEquals("Günlük limit aşılıb", state.error)
assertFalse(state.isLoading)
}
}
}A ViewModel unit test: TestDispatcher + Turbine
The balanced answer to "what do you test?": we test risk, not every line: money math and limit logic — 100%; ViewModel state transitions — the main scenarios; mappers — against sample JSONs; UI — only critical flows (login, transfer confirmation). The coverage number is an outcome, not a goal — citing the anti-example "90% coverage but untested money logic" lands well in interviews.
🛠 Practice task
Create a leak, then catch it:
- Leak an activity context through a static field on purpose, read the LeakCanary trace and fix it.
- Write a ViewModel unit test:
StandardTestDispatcher+runTest, covering the error path with a fake repository. - Get one Room DAO test passing against an in-memory DB.
Done when: all three tests are green and you can narrate the leak trace.
📚 Sources and documentation
- Testing on Androidofficialdeveloper.android.com
- LeakCanaryofficialsquare.github.io
- Testing Kotlin coroutinesofficialdeveloper.android.com