How to Test ViewModels
Guide to testing ViewModels with StateFlow and coroutines.
Basic ViewModel Test
class MyViewModelTest {
private lateinit var viewModel: MyViewModel
private lateinit var mockRepository: MyRepository
@Before
fun setup() {
mockRepository = mockk()
viewModel = MyViewModel(mockRepository)
}
@Test
fun `initial state is correct`() {
val state = viewModel.uiState.value
assertTrue(state.items.isEmpty())
assertFalse(state.isLoading)
assertNull(state.error)
}
@Test
fun `loadData updates state with success`() = runTest {
// Arrange
val testData = listOf(Item(1, "Test"))
coEvery { mockRepository.getData() } returns Result.success(testData)
// Act
viewModel.loadData()
advanceUntilIdle() // Wait for coroutines
// Assert
val state = viewModel.uiState.value
assertEquals(testData, state.items)
assertFalse(state.isLoading)
assertNull(state.error)
}
}Testing StateFlow
Collecting Multiple Emissions
@Test
fun `stateFlow emits loading then success`() = runTest {
val emissions = mutableListOf<UiState>()
val job = launch {
viewModel.uiState.collect { emissions.add(it) }
}
viewModel.loadData()
advanceUntilIdle()
job.cancel()
// Should have: initial, loading, success
assertEquals(3, emissions.size)
assertFalse(emissions[0].isLoading) // Initial
assertTrue(emissions[1].isLoading) // Loading
assertFalse(emissions[2].isLoading) // Success
}Testing with TurbineGood practice to limit the risk associated with a database migration. This involves testing the migration before it runs live.
When is the schema export useful?
Database builders can be configured to export the schema information into a file. While it’s possible to change the schema definition later, it’s best practice to limit the risk associated with a database migration. This involves testing the migration before it runs live.
When is the schema export useful?
Testing State Updates
@Test
fun `onItemClicked updates selected item`() {
val item = Item(1, "Test")
viewModel.onItemClicked(item)
assertEquals(item, viewModel.uiState.value.selectedItem)
}Testing Coroutines in ViewModels
@Test
fun `operation launches in viewModelScope`() = runTest {
coEvery { mockRepository.save(any()) } returns Result.success(Unit)
viewModel.saveData("test")
advanceUntilIdle()
coVerify { mockRepository.save("test") }
}Best Practices
- Use
runTestfor coroutine testing - Use
advanceUntilIdle()to wait for coroutines - Test state transitions (loading → success → idle)
- Mock repositories and services
- Test error handling alongside success cases
Related Documentation
Last Updated: 2025-11-01