Lesson 14 gave you the basics. This lesson covers what teams use every day: good test doubles, UI tests, debugging tools and the performance work that makes an app feel fast.
Unit tests with fakes and MockK
A test should check one behaviour. To isolate a class, replace its dependencies with test doubles. A *fake* is a simple working version (an in-memory repository). A *mock* records calls and returns answers you set. MockK is the Kotlin library for mocks, and Mockito is the Java-based alternative. Prefer fakes for repositories, and mocks for things that are hard to fake.
class FakeRepo : OrderRepository {
var placed = mutableListOf<List<Item>>()
override suspend fun place(cart: List<Item>): Result<OrderId> {
placed.add(cart); return Result.success(OrderId("A1"))
}
}
@Test fun emptyCartIsRejected() = runTest {
val useCase = PlaceOrder(FakeRepo())
assertTrue(useCase(emptyList()).isFailure)
}
@Test fun callsRepoOnce() = runTest {
val repo = mockk<OrderRepository>()
coEvery { repo.place(any()) } returns Result.success(OrderId("A1"))
PlaceOrder(repo)(listOf(item))
coVerify(exactly = 1) { repo.place(any()) }
}
runTest from kotlinx-coroutines-test runs suspend code and skips delays. Test Flows with the Turbine library (flow.test { awaitItem() }).
UI tests
- Compose UI tests:
createComposeRule(),onNodeWithText("Save").performClick(),assertIsDisplayed(). AddModifier.testTag("list")where text is not enough. - Espresso tests XML views:
onView(withId(R.id.save)).perform(click()). It still matters for older apps. - Integration tests check several parts together, for example a ViewModel with a real in-memory Room database (
Room.inMemoryDatabaseBuilder). - End-to-end tests run a full user flow on a device or emulator. They are slow, so keep a few for the key paths such as login and purchase.
- Test-driven development: write a failing test, make it pass, then tidy. It suits business rules such as price calculations.
Debugging
- Logcat: filter by package and level; use
Log.d(TAG, message). Read crash stack traces from the bottom of the "Caused by" chain up to the first line from your package. - Breakpoints and the debugger: step through code and inspect values; use conditional breakpoints in loops.
- Layout Inspector: shows the UI tree and, in Compose, how often each composable recomposes.
- Network Inspector: see requests and responses.
- App Inspection also shows the database and background tasks.
Memory, ANRs and profiling
A memory leak happens when something holds a reference to an object that should be freed, such as a static field pointing to an Activity. LeakCanary (a debug dependency) detects leaks and prints the chain. An ANR (Application Not Responding) appears when the main thread is blocked for too long, usually by disk or network work on the main thread; read the trace in Play Console or /data/anr and move the work to a background coroutine. The Android Profiler shows CPU, memory, network and energy over time.
Faster startup and smoother UI
- Startup: do the minimum in
Application.onCreate, initialise libraries lazily or with App Startup, and use the SplashScreen API. - Baseline Profiles: a file that tells Android which code to pre-compile, which can improve startup and first scrolls noticeably. Generate it with the Macrobenchmark library.
- Macrobenchmark measures startup and scrolling of the whole app. Microbenchmark measures one function.
- UI: stable keys, avoid unnecessary recomposition, avoid nested scrolling lists, and load images at display size.
- Battery and network: batch requests, use caching, avoid wake locks, and prefer WorkManager.
- Size: enable R8 and resource shrinking, use AAB, compress images (WebP), and remove unused libraries. Check the APK Analyzer.
Your task
- Write a fake repository and test a use case with it.
- Write one Compose UI test that types in a field and presses a button.
- Add LeakCanary to a debug build and look for a leak you create on purpose.
- Run the APK Analyzer on a release build and list the three largest items.
How to check it worked
- Tests do not depend on the network.
- You can read a crash trace to your own code.
- You can name three ways to speed up startup.