Using Application.Current.Dispatcher during tests

87 Views Asked by At

In a lot of our viewmodels, we have code that looks something like this :

void Process()
{
    // do some work
    ...

    // Dispatch some UI notification (purely visual related)
    Application.Current.Dispatcher.BeginInvoke(() => ...);
}

We use Application.Dispatcher to ensure it runs on the UI thread, as Dispatcher.Current might not be the UI dispatcher.

The reason for dispatching the call is irrelevant and doesn't affect control flow or logic, however, when we test the viewmodel, we obviously have the issue that Application.Current is null.

Assuming the code being dispatched has no effect on the test. What to you think is the best way to get around the issue of it being null?

I can think of a few

  1. Simply use Application.Current?.Dispatcher(). Is this a code smell?
  2. Have some kind of mockable IApplicationDispatcher that every ViewModel has access to. This gives us the most power, but at the same time, it's annoying to now have an extra constructor parameter for each viewmodel.
  3. Ensure tests run with a new application being created on initialise. This is ugly, and would need to be a static application that all UI tests share.
2

There are 2 best solutions below

0
AudioBubble On

The Dispatcher is usually not required in the view model. This is because the view model should not handle UI elements in general. In other words types that extend DispatcherObject should not be referenced outside the view.

When your view model class implements INotifyPropertyChanged setting a property from a background thread does not require the Dispatcher. The binding engine will marshal the INotifyPropertyChanged.PropertyChanged event to the correct dispatcher thread (UI thread).

This does not apply for the INotifyCollectionChanged.CollectionChanged event. You would have to configure the binding engine explicitly to marshal the event to the dispatcher thread by using the BindingOperations.EnableCollectionSynchronization method. This way the binding engine will take care to raise the INotifyCollectionChanged.CollectionChanged event on the correct dispatcher thread:

class ViewModel
{
  public ObservableCollection<object> BindingSourceCollection { get; }
  private object SyncLock { get; }

  public ViewModel()
  {
    this.SyncLock = new object();
    this.BindingSourceCollection = new ObservableCollection<object>();    

    // The call must occur on the UI thread.
    // The call must occur before using the collection on a different thread. 
    // The call must occur before attaching the collection to the ItemsControl.
    BindingOperations.EnableCollectionSynchronization(this.BindingSourceCollection, this.SyncLock);
  }
}

This way the most common uses of the Dispatcher in the view model (set property that is a binding source and add item to a collection that is a binding source) are eliminated. This removes the static Singleton reference from your code to enable full testability.

0
mm8 On

You should use an IApplicationDispatcher interface (option 2). It doesn't necessarily have to be a constructor parameter. You could for example add it as a property on the base view model class and set it as needed in your tests.

Just make sure to set the property to a wrapper around Application.Current.Dispatcher in your real app.

Calling static methods in your view models doesn't make them very unit test friendly I am afraid.

Checking for null references (option 1) is not a code smell. You should probably do this as well.