Testing Strategy & Pyramid Quiz
TESTING › Strategy
According to the test pyramid, which type of test should you have the most of?
- End-to-end UI tests
- Unit tests
- Manual exploratory tests
- Screenshot tests
Answer: Unit tests
Unit tests form the base of the pyramid because they are fast, isolated, and stable, so you write many of them and far fewer slow UI tests.
Which statement best describes the tradeoff between local and instrumented tests?
- Instrumented tests run faster, but they have lower fidelity than local tests.
- Local tests run slower, but they are more reliable than instrumented tests.
- Local tests run faster, while instrumented tests have higher fidelity.
- There is no meaningful difference once both kinds of tests pass successfully.
Answer: Local tests run faster, while instrumented tests have higher fidelity.
Local JVM tests run quickly without a device, while instrumented tests run on a device/emulator with the real framework, giving higher fidelity at the cost of speed.
What is the recommended way to control coroutines when unit-testing a ViewModel?
- Call Thread.sleep to wait for background work to finish
- Inject a test dispatcher and run the test with runTest
- Use the real Dispatchers.Main on the main thread
- Mark every test function with @LargeTest
Answer: Inject a test dispatcher and run the test with runTest
Injecting a TestDispatcher and using runTest gives deterministic, virtual-time control over coroutines, avoiding flaky sleeps and real-thread timing.
Which of these is the best candidate to NOT write a dedicated test for?
- A ViewModel's state calculation after handling an error case
- A trivial auto-generated data class with no custom logic
- A repository's caching and error-mapping logic in a data layer
- A use case that merges data from two different sources together
Answer: A trivial auto-generated data class with no custom logic
Trivial generated code with no logic provides little value to test; effort is better spent on your own logic, state changes, and edge cases.
When testing a ViewModel in isolation, how should its repository dependency typically be provided?
- As a fake or test double that implements the repository interface
- As the real repository, even if it talks to the production network
- As a static singleton that the ViewModel looks up directly
- As an Android Context-backed class tied to the app runtime
Answer: As a fake or test double that implements the repository interface
A fake implementation of the repository interface isolates the ViewModel from real I/O, keeping the test fast, deterministic, and a local test.
Which choice best distinguishes a fake from a mock?
- A fake can only ever be used inside instrumented on-device tests
- A mock is always faster to run than an equivalent real fake
- A fake is a real working implementation; a mock is a scripted stub
- They are basically identical, so the two terms are fully interchangeable
Answer: A fake is a real working implementation; a mock is a scripted stub
A fake is a functional in-memory implementation you use normally, whereas a mock is a framework-configured object whose calls and return values you script and verify.
Why does keeping business logic out of Activities, Fragments, and the Android framework improve your test strategy?
- It forces all logic into instrumented UI tests for higher fidelity
- It lets you swap dependencies and unit-test logic as fast tests
- It removes the need for integration or UI tests in your app entirely
- It makes the app run faster in production but does not affect testing
Answer: It lets you swap dependencies and unit-test logic as fast tests
Decoupling logic from framework classes and using interfaces/DI lets you replace dependencies and verify logic in fast, isolated local tests rather than being forced into slow instrumented tests.
What is the key behavioral difference between UnconfinedTestDispatcher and StandardTestDispatcher in coroutine tests?
- UnconfinedTestDispatcher starts eagerly; StandardTestDispatcher queues until advanced
- StandardTestDispatcher uses a real background thread, while UnconfinedTestDispatcher doesn't
- UnconfinedTestDispatcher is limited to instrumented device tests, not unit tests
- They are just aliases for the same dispatcher and behave the same way
Answer: UnconfinedTestDispatcher starts eagerly; StandardTestDispatcher queues until advanced
UnconfinedTestDispatcher launches new coroutines eagerly and runs them until their first suspension, whereas StandardTestDispatcher defers them onto its scheduler until you advance virtual time.
You need to assert the exact sequence of values a Flow emits in a fast unit test. Which approach is most idiomatic?
- Insert Thread.sleep between emissions and read only the latest value
- Render the Flow in a real UI and inspect the result on screen
- Use Turbine to await and assert each item inside runTest
- Override equals() on the Flow so emissions compare automatically
Answer: Use Turbine to await and assert each item inside runTest
Turbine collects a Flow within runTest and lets you await each item deterministically, which is far more reliable than sleeping and sampling only the latest value.
What is Robolectric primarily used for?
- Recording and replaying network traffic during instrumented tests on devices
- Generating reference screenshots from Compose previews for visual regression checks
- Replacing Gradle as the test build and dependency system for Android projects
- Running Android framework tests on the host JVM without a device or emulator
Answer: Running Android framework tests on the host JVM without a device or emulator
Robolectric provides a simulated Android framework on the JVM, letting tests that touch SDK classes run as fast host-side tests instead of slow instrumented ones.
What do the integration tests in the middle of the test pyramid primarily verify?
- That a single pure function returns one correct value in isolation
- That several layers collaborate, e.g. a repo and a real Room DB
- Only the pixel-level visual appearance of one finished screen
- That a third-party library's internals are themselves free of bugs
Answer: That several layers collaborate, e.g. a repo and a real Room DB
Integration tests sit between unit and UI tests and confirm that several components wired together behave correctly, for example a repository plus its real DAO and database.
In a Jetpack Compose UI test, how do you locate and assert on a component?
- Use createComposeRule, query the semantics tree, and assert on nodes
- Cast the composable to a View and call findViewById on it directly
- Capture a screenshot, then use OCR to read text and verify the component
- Inspect recomposition counts via reflection to locate the UI element
Answer: Use createComposeRule, query the semantics tree, and assert on nodes
Compose tests use a compose test rule and query the semantics tree with finders like onNodeWithText, then chain assertions and actions on the matched node.
A module reports 95% line coverage. What can you correctly conclude from that number alone?
- The module is guaranteed to be entirely free of bugs on every code path
- Every edge case and branch in the module is definitely covered by a test
- Lines ran in tests, but nothing about assertion quality or edge cases
- The tests behind it must all be unit tests rather than slower UI tests
Answer: Lines ran in tests, but nothing about assertion quality or edge cases
Coverage only measures which lines ran during tests; code can be executed without any assertion verifying its behavior, so high coverage does not imply correctness or good edge-case handling.
Kotlin classes and methods are final by default. What does this imply when choosing a mocking approach?
- Mocking is impossible in Kotlin, so hand-written fakes are the only option
- Mark every production class open so plain Mockito can mock it in tests
- Only instrumented device tests can mock Kotlin classes without extra tools
- Prefer MockK or Mockito inline, since final classes need special support
Answer: Prefer MockK or Mockito inline, since final classes need special support
Because Kotlin classes are final unless marked open, plain Mockito cannot mock them without the inline mock maker; MockK is built for Kotlin and handles final classes out of the box.