android-testinglisted
Install: claude install-skill lenorebreakneck630/claude-zero-to-hero-android-KMP
# Android / KMP Testing
## Stack
| Concern | Library |
|---|---|
| Test framework | JUnit5 |
| Assertions | AssertK |
| Flow / StateFlow testing | Turbine |
| Coroutine testing | `kotlinx-coroutines-test` + `UnconfinedTestDispatcher` |
| UI testing | `ComposeTestRule` |
---
## ViewModel Unit Tests
### Setup
```kotlin
class NoteListViewModelTest {
private val testDispatcher = UnconfinedTestDispatcher()
@BeforeEach
fun setUp() {
Dispatchers.setMain(testDispatcher)
}
@AfterEach
fun tearDown() {
Dispatchers.resetMain()
}
}
```
### Testing State with Turbine
```kotlin
@Test
fun `loading notes emits notes in state`() = runTest {
val repo = FakeNoteRepository()
val viewModel = NoteListViewModel(repo)
viewModel.state.test {
viewModel.onAction(NoteListAction.OnRefreshClick)
assertThat(awaitItem().isLoading).isTrue()
assertThat(awaitItem().notes).isNotEmpty()
}
}
```
### Testing Events (one-time side effects)
```kotlin
@Test
fun `clicking note sends NavigateToDetail event`() = runTest {
val viewModel = NoteListViewModel(FakeNoteRepository())
viewModel.events.test {
viewModel.onAction(NoteListAction.OnNoteClick("123"))
assertThat(awaitItem()).isEqualTo(NoteListEvent.NavigateToDetail("123"))
}
}
```
---
## Fake Repositories
Prefer **fakes** (not mocks) for repository dependencies. A fake is a simple in-memory implementation:
```kotlin
class F