Irbisa · Cheatsheet
Android Testing
JUnit, MockK, Turbine, Robolectric, Espresso, Compose UI tests, Hilt rules, screenshot tests
What is the difference between unit, instrumented and UI tests, and what belongs in
src/testversussrc/androidTest?What are test doubles (dummy, stub, fake, mock, spy), and when do you prefer a fake over a mock?
How do you structure a good JUnit test: naming, Given/When/Then, assertions, and
@Beforeversus@BeforeClass?// wrong: two actions, boolean assertions, a name that describes the method@Test fun testCart() { val cart = Cart() cart.add(Item("apple", cents = 100)) cart.applyCoupon("HALF") assertTrue(cart.totalCents == 50) // fails with a bare AssertionError…What is TDD, and what testing rules would you set for an Android team?
Why does a JVM unit test fail with
Method ... in android.util.Log not mocked, and how do you fix it?// fails on the JVM: Log and Uri are stubs in src/testclass DeepLinkParser { fun orderId(link: String): Long? { Log.d("DeepLink", "parsing $link") // RuntimeException: Method d ... not mocked return Uri.parse(link).getQueryParameter("order")?.toLongOrNull() }…How do you click an item in a
RecyclerViewwith Espresso, including one that is not on screen yet?// androidTestImplementation("androidx.test.espresso:espresso-contrib:<version>") // by positiononView(withId(R.id.orders)) .perform(RecyclerViewActions.actionOnItemAtPosition<OrderViewHolder>(3, click()))…What is UI Automator, and when do you use it instead of Espresso?
@RunWith(AndroidJUnit4::class)class ScanFlowTest { @get:Rule val activity = ActivityScenarioRule(MainActivity::class.java) private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) @Test fun scanAfterGrantingCamera() {…Mockito vs MockK: how do you mock final classes and
suspendfunctions, and what doesrelaxed = truedo?class ArchiveUseCaseTest { private val repo = mockk<OrderRepo>(relaxUnitFun = true) // strict except Unit functions @Test fun `archives the loaded order`() = runTest { coEvery { repo.load(7) } returns Order(id = 7, archived = false) // suspend: coEvery val saved = slot<Order>()…How do you unit-test a ViewModel with coroutines:
runTest,Dispatchers.setMain, andStandardTestDispatcherversusUnconfinedTestDispatcher?class MainDispatcherRule( val dispatcher: TestDispatcher = StandardTestDispatcher(),) : TestWatcher() { override fun starting(description: Description) = Dispatchers.setMain(dispatcher) override fun finished(description: Description) = Dispatchers.resetMain()}…This test never finishes and fails on a timeout. Why, and how would you test the
StateFlowinstead?@get:Rule val mainRule = MainDispatcherRule() @Test fun `search emits loading then results`() = runTest { val vm = SearchViewModel(FakeRepo()) // state: StateFlow<UiState> vm.search("room") val states = vm.state.toList() assertEquals( listOf(UiState.Loading, UiState.Results(listOf("Room"))), states, )}Find the bug: this ViewModel test passes locally but fails on CI about half the time.
class ProfileViewModel(private val repo: ProfileRepo) : ViewModel() { val state = MutableStateFlow<UiState>(UiState.Loading) fun load() = viewModelScope.launch { val name = withContext(Dispatchers.IO) { repo.profile() } state.value = UiState.Loaded(name) }} @Test fun `load shows the profile`() = runTest { // MainDispatcherRule is set val vm = ProfileViewModel(FakeProfileRepo()) vm.load() advanceUntilIdle() assertTrue(vm.state.value is UiState.Loaded)}Find the bug: this test still passes after someone deletes the
emitfrompay().class CheckoutViewModel : ViewModel() { private val _events = MutableSharedFlow<Event>() val events: SharedFlow<Event> = _events fun pay() = viewModelScope.launch { _events.emit(Event.OpenReceipt) }} @Test fun `pay opens the receipt`() = runTest { // MainDispatcherRule is set val vm = CheckoutViewModel() backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { vm.events.collect { assertEquals(Event.OpenReceipt, it) } } vm.pay() advanceUntilIdle()}How do you unit-test a ViewModel that uses
SavedStateHandle, needs strings from aContext, or observes the lifecycle?// shown as an observer only to demonstrate TestLifecycleOwnerclass OrderViewModel( savedState: SavedStateHandle, private val repo: OrderRepository,) : ViewModel(), DefaultLifecycleObserver { val orderId: Long = checkNotNull(savedState["orderId"])…What is Robolectric, and what can it not prove compared with an instrumented test?
@RunWith(AndroidJUnit4::class) // Robolectric, because the file is in src/test@Config(sdk = [35])class ReceiptFormatterTest { private val context: Context = ApplicationProvider.getApplicationContext() @Test fun formatsFromRealStringResources() {…What does Espresso wait for automatically, and when do you need an
IdlingResource?// Ships in the app; the release build can use a no-op implementationobject AppIdling { val network = CountingIdlingResource("network")} class SearchRepository(private val api: Api) {…This Compose test fails with "could not find any node" although the price is visible on screen. Why, and how do you fix it?
@Composablefun OrderRow(order: Order, onOpen: () -> Unit) { Button(onClick = onOpen) { Column { Text(order.title) Text(order.priceLabel, Modifier.testTag("price")) } }} @Test fun showsPrice() { rule.setContent { OrderRow(Order(42, "Socks", "12.00 €"), onOpen = {}) } rule.onNodeWithTag("price").assertTextEquals("12.00 €")}How do you test a WorkManager worker, and how do you prove its constraints actually gate it?
@RunWith(AndroidJUnit4::class)class UploadWorkTest { private val context: Context = ApplicationProvider.getApplicationContext() @Before fun setUp() { val config = Configuration.Builder()…How do screenshot tests work on Android (Paparazzi, Roborazzi, on-device), and what keeps a golden image stable?
// Paparazzi: layoutlib on the JVM, the device is a data classclass OrderCardTest { @get:Rule val paparazzi = Paparazzi( deviceConfig = DeviceConfig.PIXEL_5.copy(fontScale = 1.0f, locale = "en"), )…What makes instrumented UI tests flaky, and how do you find and fix the cause?
What does code coverage actually tell you, and what would you gate a CI build on?