Skip to content

Test ViewModel

Ali Sadeghi edited this page Feb 5, 2026 · 7 revisions

Test ViewModel Agent

Generates ViewModel tests using Turbine for StateFlow testing.

Spawned by: feature-test command

What It Tests

  • Initial state (Uninitialized or Loading)
  • State transitions (Loading → Success, Loading → Failed)
  • Retry flow (Failed → Loading → Success)
  • User actions and their effects
  • Rapid action debouncing
  • Input validation (if applicable)
  • Dialog state management (if applicable)

Example Output

@OptIn(ExperimentalCoroutinesApi::class)
class LoginViewModelTest {
    private val testDispatcher = StandardTestDispatcher()
    private val repository = mock<LoginRepository>()
    private lateinit var viewModel: LoginViewModel

    @BeforeTest
    fun setup() {
        Dispatchers.setMain(testDispatcher)
    }

    @AfterTest
    fun teardown() {
        Dispatchers.resetMain()
        resetAnswers(repository)
    }

    private fun createViewModel() {
        viewModel = LoginViewModel(repository)
    }

    @Test
    fun `login emits Loading then Success on success`() = runTest {
        everySuspend { repository.login(any()) } returns
            LoginFixtures.createSuccessUser()

        createViewModel()

        viewModel.uiModelState.test {
            var current = awaitItem()
            if (current.state is UiState.Uninitialized) {
                current = awaitItem()
            }
            assertTrue(current.state is UiState.Loading)

            viewModel.login("test@example.com", "password")
            advanceUntilIdle()

            current = expectMostRecentItem()
            assertTrue(current.state is UiState.Success)

            cancelAndIgnoreRemainingEvents()
        }
    }

    @Test
    fun `retry after failure transitions to Success`() = runTest {
        everySuspend { repository.login(any()) } sequentiallyReturns listOf(
            LoginFixtures.createFailureUser(),
            LoginFixtures.createSuccessUser()
        )

        createViewModel()

        viewModel.uiModelState.test {
            // Wait for initial failure
            var current = awaitItem()
            while (current.state !is UiState.Failed) {
                advanceUntilIdle()
                current = awaitItem()
            }

            // Retry
            viewModel.retry()
            advanceUntilIdle()

            current = expectMostRecentItem()
            assertTrue(current.state is UiState.Success)

            cancelAndIgnoreRemainingEvents()
        }
    }
}

Critical Rule: advanceUntilIdle()

MUST be called immediately after any method that triggers coroutines:

// WRONG - after ViewModel creation
createViewModel()
advanceUntilIdle() // Don't do this!

// CORRECT - after method call
viewModel.retry()
advanceUntilIdle() // Immediately after!

Back to Testing Agents | Agents

Clone this wiki locally