Android Kotlin — Testing Module
Parent kit: ../../SKILL.md · Index: ../../AGENTS.md
Read First
| File | When |
|---|---|
../../references/11-testing.md |
Turbine, fakes, Compose test tags |
../../references/05-hilt-di.md |
TestInstallIn modules |
../../references/13-release-checklist.md |
Pre-ship checklist |
ViewModel Test Template
@OptIn(ExperimentalCoroutinesApi::class)
class FeatureViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
private val fakeRepo = FakeFeatureRepository()
private lateinit var viewModel: FeatureViewModel
@Before
fun setup() {
viewModel = FeatureViewModel(fakeRepo)
}
@Test
fun `refresh success clears loading`() = runTest {
fakeRepo.syncResult = Result.success(Unit)
viewModel.onEvent(FeatureUiEvent.Refresh)
viewModel.state.test {
skipItems(1)
val loading = awaitItem()
assertTrue(loading.isLoading)
val done = awaitItem()
assertFalse(done.isLoading)
assertNull(done.error)
}
}
}
Fake Over Mock
class FakeFeatureRepository : FeatureRepository {
var syncResult: Result<Unit> = Result.success(Unit)
private val items = MutableStateFlow<List<Feature>>(emptyList())
override fun observeItems(): Flow<List<Feature>> = items
override suspend fun sync(): Result<Unit> = syncResult
}
Prefer fakes implementing real interfaces — tests behavior, not interaction order.
Compose UI Test
class FeatureScreenTest {
@get:Rule
val composeRule = createComposeRule()
@Test
fun showsEmptyState() {
composeRule.setContent {
AppTheme {
FeatureContent(
state = FeatureUiState(items = emptyList()),
)
}
}
composeRule.onNodeWithTag("feature_empty").assertIsDisplayed()
}
}
Test stateless FeatureContent, not ViewModel-integrated screen.
What to Test vs Skip
| Always test | Usually skip |
|---|---|
| ViewModel state transitions | Room generated SQL |
| Repository mappers | Hilt graph wiring (compile-time) |
Error paths (Result.failure) |
Pixel-perfect screenshots |
| Critical user flows (instrumented) | Third-party SDK internals |
CI Gate
Repo skill validation must pass before merge:
python3 scripts/validate_skills.py --strict