Skip to content
2 min read

Testing WPF ViewModels Through Explicit Service Boundaries

Using dependency injection and a controllable service implementation to reproduce UI states and test ViewModel behavior.

  • #wpf
  • #dotnet
  • #csharp
  • #testing
  • #architecture

ViewModel behavior is difficult to verify when every scenario depends on a live API or a specific database state. A controllable service implementation gives development and testing a repeatable way to exercise those states.

Separate ViewModel behavior from external I/O

In this example, the ViewModel depends on IWeatherService. The production implementation retrieves weather data; a controllable implementation returns a temperature selected by the developer.

The ViewModel uses the same contract in both cases. This lets the test setup vary the input without changing the logic under examination.

UML class diagram showing the MainViewModel depending on the IWeatherService interface, with WeatherApiService and MockWeatherService as implementations.
The ViewModel depends on the IWeatherService contract.

Select the implementation at startup

The application uses the .NET Host and dependency injection to select the service during startup. A MockEnabled setting chooses the controllable implementation for development.

Keep that decision in the composition root. The ViewModel should not branch on whether it is running against the real service. Scope the setting appropriately so a development configuration cannot silently substitute simulated data in a production deployment.

UML sequence diagram illustrating the application startup, showing the Host configuring services and injecting the chosen IWeatherService into the MainViewModel.
Service registration selects the implementation during startup.

Provide a focused way to exercise states

A development panel uses IMockManager to set the temperature returned by the service. Selecting a hot or cold value then exercises the ViewModel's display logic through the normal refresh path.

This is a manual verification aid. Automated tests should also check the meaningful outcomes: property values, change notifications, command availability, and the transitions between loading, success, and failure.

A service that returns configured data is a test double; it does not need a mocking framework. What matters is that it implements a clear contract and can reproduce the condition being tested.

Preserve the boundary's limits

Tests against a substitute service verify the ViewModel's response to that service contract. Integration tests are still needed for serialization, authentication, network failures, and the production service itself.

The design principle is straightforward: make application states reproducible, keep external dependencies replaceable where useful, and verify each layer at the boundary it owns.