Unit Testing ViewModels with Turbine and MockK
Write comprehensive ViewModel tests using modern Kotlin testing tools.
MainDispatcherRule Setup
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
@Test
fun testUserLoading() = runTest {
// Automatically uses TestDispatchers
}
MockK coEvery/coVerify
val mockRepository = mockk<UserRepository>()
coEvery { mockRepository.fetchUser(any()) } returns User(1, "Alice")
val viewModel = UserViewModel(mockRepository)
coVerify { mockRepository.fetchUser("123") }
Turbine Flow Testing
@Test
fun testUiStateFlow() = runTest {
turbineScope {
val uiStateTurbine = viewModel.uiState.testIn(backgroundScope)
assertEquals(UiState.Loading, uiStateTurbine.awaitItem())
assertEquals(UiState.Success(user), uiStateTurbine.awaitItem())
uiStateTurbine.ensureAllEventsConsumed()
}
}
StateFlow State Transitions
@Test
fun testStateTransitions() = runTest {
viewModel.loadUser("123")
advanceTimeBy(100) // Simulate time passage
assertEquals(User(1, "Alice"), viewModel.user.value)
}
SavedStateHandle Testing
val savedStateHandle = SavedStateHandle(mapOf("userId" to "123"))
val viewModel = UserViewModel(
repository = mockRepository,
savedStateHandle = savedStateHandle
)
Test Structure
- Setup mocks with coEvery
- Create ViewModel with dependencies
- Trigger action
- Verify state changes with Turbine
- Verify interactions with coVerify
Common Assertions
- awaitItem(): Get next emitted value
- awaitComplete(): Wait for completion
- ensureAllEventsConsumed(): Verify no leftover events
- skipItems(n): Skip n events
Best Practices
- Test state transitions, not implementation
- Use Turbine for Flow/StateFlow testing
- Mock external dependencies
- Test error cases and edge cases
8 Android app templates on Gumroad
Top comments (0)