100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
.NET

Testing ViewModels

How to unit test MVVM ViewModels in isolation, covering state assertions, command execution, async flows, and dependency mocking.

Practical MVVMIntermediate9 min readJul 10, 2026
Analogies

Why ViewModels Are Built for Unit Testing

A ViewModel in MVVM holds no direct reference to any View, Activity, Fragment, or UIViewController type; it only exposes plain properties, observables, and commands. Because it depends on nothing from the UI framework, it can be instantiated inside a plain JVM/CLR/Swift unit test process, with no emulator, simulator, or UI thread required. This isolation is the entire reason MVVM is described as 'testable by design'.

🏏

Cricket analogy: It's like a batting coach who reviews a player's technique from video footage alone, never needing the player physically present to analyze foot movement, timing, and shot selection.

Asserting State Changes with Given-When-Then

The standard unit-test pattern for a ViewModel is given-when-then: construct the ViewModel with fake or stub dependencies, invoke a method or trigger a command, then assert that an observable property (a LiveData value, a StateFlow's current value, an ObservableObject's @Published field) equals the expected result. Frameworks require supporting utilities such as InstantTaskExecutorRule on Android, or synchronous test schedulers, to force LiveData and coroutine dispatchers to execute immediately rather than on a background thread.

🏏

Cricket analogy: It's like a DRS review: given the ball's trajectory (setup), when it strikes the pad (action), then Hawk-Eye confirms whether it would have hit the stumps (assertion) — a precise, repeatable check.

kotlin
class CartViewModelTest {
    @get:Rule val instantTaskRule = InstantTaskExecutorRule()
    private val testDispatcher = StandardTestDispatcher()

    @Test
    fun `applying a valid coupon updates total`() = runTest(testDispatcher) {
        val fakeRepo = FakeCartRepository(basePrice = 100.0)
        val viewModel = CartViewModel(fakeRepo, testDispatcher)

        viewModel.applyCoupon("SAVE10")
        advanceUntilIdle()

        assertEquals(90.0, viewModel.uiState.value.total, 0.01)
        assertFalse(viewModel.uiState.value.isLoading)
    }
}

Mocking Dependencies and Services

ViewModels should accept their dependencies — repositories, use cases, network clients — through the constructor rather than instantiating them internally, a practice enabled by dependency injection frameworks like Hilt, Koin, or a .NET DI container. In a test, those same constructor parameters are swapped for fakes or mocks built with Mockito, MockK, or Moq, which let the test control exactly what data comes back and verify which methods were called, without ever touching a real network or database.

🏏

Cricket analogy: It's like a stand-in bowling machine used at nets: the batter (ViewModel) trains against a controllable, repeatable ball delivery instead of an unpredictable live bowler, so technique can be verified precisely.

Never let a ViewModel unit test depend on a real Android Context, real UIKit classes, or an actual network connection. If a test needs any of those, the dependency boundary was drawn in the wrong place — push platform-specific code behind an interface the ViewModel can fake.

Testing Asynchronous Operations Deterministically

When a ViewModel launches coroutines, async/await tasks, or Combine publishers, tests must replace the real dispatcher or scheduler with a test-controlled one — such as Kotlin's StandardTestDispatcher inside runTest, or a TestScheduler for RxJava — so that virtual time advances deterministically instead of relying on real delays. This lets a test assert the full sequence of UI states, for example Loading, then Success or Error, without flakiness caused by real threading race conditions.

🏏

Cricket analogy: It's like using Duckworth-Lewis-Stern calculations to deterministically resolve a rain-affected match instead of waiting for real weather, producing a repeatable, testable result every time.

Testing a ViewModel by driving the full UI through Espresso, XCUITest, or a WPF UI automation tool is slow and brittle — a single layout change can break dozens of tests that had nothing to do with ViewModel logic. Reserve UI tests for a handful of critical end-to-end flows and push the bulk of logic verification into fast ViewModel unit tests.

  • ViewModels hold no reference to View/UI framework types, so they run in plain unit tests without an emulator or simulator.
  • Use given-when-then: construct with fakes, trigger a command, assert on the resulting observable state.
  • Force synchronous execution with tools like InstantTaskExecutorRule or a test coroutine dispatcher.
  • Inject dependencies through the constructor so tests can substitute mocks/fakes (Mockito, MockK, Moq).
  • Use test dispatchers/schedulers (StandardTestDispatcher, TestScheduler) to control virtual time for async code.
  • Assert the full state sequence (Loading -> Success/Error), not just the final value, to catch intermediate-state bugs.
  • Keep UI-automation tests few and reserved for critical flows; let fast ViewModel unit tests cover the bulk of logic.

Practice what you learned

Was this page helpful?

Topics covered

#NET#MVVMDesignPatternStudyNotes#MicrosoftTechnologies#TestingViewModels#Testing#ViewModels#Built#Unit#StudyNotes#SkillVeris