Create comprehensive unit tests for a ViewModel or Repository.
Generates comprehensive unit tests for ViewModels or Repositories using mockk and turbine. Use this when you need to test state management, actions, error handling, or data layer caching patterns.
/plugin marketplace add ggz23/android-interview-plugin/plugin install ggz23-android-interview@ggz23/android-interview-pluginCreate comprehensive unit tests for a ViewModel or Repository.
$ARGUMENTS - Class to test (e.g., "BookingViewModel", "PatientRepository")
Create test file in test/java/.../presentation/{name}/{Name}ViewModelTest.kt
Setup structure:
@OptIn(ExperimentalCoroutinesApi::class)
class {Name}ViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
// Mock all constructor dependencies
private val repository: {Name}Repository = mockk()
private lateinit var viewModel: {Name}ViewModel
@Before
fun setup() {
// Stub default responses BEFORE creating ViewModel
coEvery { repository.getItems() } returns flowOf(emptyList())
viewModel = {Name}ViewModel(repository)
}
}
Initial State Test:
@Test
fun `initial state is correct`() = runTest {
viewModel.state.test {
val state = awaitItem()
assertEquals(emptyList<Item>(), state.items)
assertFalse(state.isLoading)
assertNull(state.error)
}
}
Action Tests:
@Test
fun `load action updates state correctly`() = runTest {
val testData = listOf(Item("1", "Test"))
coEvery { repository.getItems() } returns flowOf(testData)
viewModel.state.test {
skipItems(1) // Skip initial state
viewModel.onAction({Name}Action.Load)
advanceUntilIdle()
val state = awaitItem()
assertEquals(testData, state.items)
}
}
Error Handling Test:
@Test
fun `handles error gracefully`() = runTest {
coEvery { repository.getItems() } throws Exception("Network error")
viewModel.state.test {
skipItems(1)
viewModel.onAction({Name}Action.Load)
advanceUntilIdle()
val state = awaitItem()
assertNotNull(state.error)
assertFalse(state.isLoading)
}
}
Create test file in test/java/.../data/repository/{Name}RepositoryTest.kt
Setup structure:
class {Name}RepositoryTest {
private val api: {Name}Api = mockk()
private val dao: {Name}Dao = mockk(relaxed = true)
private lateinit var repository: {Name}Repository
@Before
fun setup() {
repository = {Name}RepositoryImpl(api, dao)
}
}
Cache First Test:
@Test
fun `getItems emits cached data first`() = runTest {
val cached = listOf({Name}Entity("1", "Cached"))
every { dao.getAll() } returns flowOf(cached)
coEvery { api.getItems() } returns listOf({Name}Dto("1", "Fresh"))
repository.getItems().test {
val items = awaitItem()
assertEquals("Cached", items.first().name)
cancelAndIgnoreRemainingEvents()
}
}
Error Fallback Test:
@Test
fun `handles API error with cache fallback`() = runTest {
val cached = listOf({Name}Entity("1", "Cached"))
every { dao.getAll() } returns flowOf(cached)
coEvery { api.getItems() } throws Exception("Network error")
repository.getItems().test {
val items = awaitItem()
assertEquals(1, items.size) // Cache still works
cancelAndIgnoreRemainingEvents()
}
}
./gradlew test| Pattern | Code |
|---|---|
| Mock creation | mockk() or mockk(relaxed = true) |
| Stub suspend | coEvery { api.fetch() } returns data |
| Stub Flow | every { dao.getAll() } returns flowOf(list) |
| Stub throw | coEvery { api.fetch() } throws Exception("err") |
| Verify called | coVerify { api.fetch() } |
| Flow test | flow.test { val item = awaitItem() } |
| Skip items | skipItems(1) |
| Stop flow | cancelAndIgnoreRemainingEvents() |