开发者

Using Dispatcher in unit-testable MVVM code

I've an MVVM-lite application that I'd like to have unit testable. The model uses a System.Timers.Timer and so the update event ends up on a background worker thread. This unit-tested fine, but at runtime threw the System.NotSupportedException "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread." I had hoped that the MVVM-lite class Threading.DispatcherHelper would fix things but calling DispatcherHelper.CheckBeginInvokeOnUI results in my unit test failing. Here's the code I've ended up with in the view model

private void locationChangedHandler(object src, LocationChangedEventArgs e)
{
    if (e.LocationName != this.CurrentPlace开发者_如何学PythonName)
    {
        this.CurrentPlaceName = e.LocationName;
        List<FileInfo> filesTaggedForHere = Tagger.FilesWithTag(this.CurrentPlaceName);

        //This nextline fixes the threading error, but breaks it for unit tests
        //GalaSoft.MvvmLight.Threading.DispatcherHelper.CheckBeginInvokeOnUI(delegate { updateFilesIntendedForHere(filesTaggedForHere); });

        if (Application.Current != null)
        {
            this.dispatcher.Invoke(new Action(delegate { updateFilesIntendedForHere(filesTaggedForHere); }));
        }
        else
        {
            updateFilesIntendedForHere(filesTaggedForHere);
        }
    }
}
private void updateFilesIntendedForHere(List<FileInfo> filesTaggedForHereIn)
{
    this.FilesIntendedForHere.Clear();
    foreach (FileInfo file in filesTaggedForHereIn)
    {
        if (!this.FilesIntendedForHere.Contains(file))
        {
            this.FilesIntendedForHere.Add(file);
        }
    }
}

I did try the trick in http://kentb.blogspot.com/2009/04/mvvm-infrastructure-viewmodel.html but the call to Invoke on the Dispatcher.CurrentDispatcher failed to run during the unit test and so it failed. That's why I'm calling the helper method directly if the run is in a test and not an application.

This can't be right - the ViewModel shouldn't care where it's being called from. Can anyone see why neither Kent Boogaart's dispatcher method nor the MVVM-lite DispatcherHelper.CheckBeginInvokeOnUI work in my unit test?


I do it like this:

class MyViewModel() {
    private readonly SynchronizationContext _syncContext;

    public MyViewModel() {
        _syncContext = SynchronizationContext.Current; // or use DI
    )

    ...

    public void SomeTimerEvent() {
        _syncContext.Post(_ => UpdateUi(), null);
    }
}

The default context will be threadpool in your test and dispatcher in UI. You can also easily create your own test context if you want some other behavior.


I do not believe there is an easy answer to this in MVVM-lite. You have the right solution of calling DispatcherHelper.CheckBeginInvokeOnUI. But, when the unit test is being run, the UI does not exist, and the DispatcherHelper won't run properly.

I use ReactiveUI. It's version of DispatcherHelper.CheckBeginInvokeOnUI (RxApp.DeferredScheduler) will check to determine if it is being run in a unit test. If it is, it will run on the current thread instead of trying to marshal to the non-existent UI thread. You may be able to use this code to build your own check into DispatcherHelper. The relevant code is in RxApp.cs method InUnitTestRunner() (line 196). It is pretty hacky, but it works and I don't think there is a better way.


I just call the Initialize method in my ViewModelUnitTestBase and it works fine. Make sure that the DispatcherHelper.UIDispatcher is not null.

public abstract class ViewModelUnitTestBase<T> where T : ViewModelBase
{
    private T _viewModel = default(T);
    public T ViewModel
    {
        get { return _viewModel; }
        set { _viewModel = value; }
    }

    static ViewModelUnitTestBase()
    {
        DispatcherHelper.Initialize();
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜