在单元可测试的MVVM代码中使用Dispatcher

时间:2011-01-24 03:36:25

标签: multithreading mvvm mvvm-light dispatcher

我有一个MVVM-lite应用程序,我想要单元可测试。该模型使用System.Timers.Timer,因此更新事件最终在后台工作线程上。这个单元测试很好,但是在运行时抛出了System.NotSupportedException“这种类型的CollectionView不支持从与Dispatcher线程不同的线程更改其SourceCollection。”我曾希望MVVM-lite类Threading.DispatcherHelper可以解决问题,但调用DispatcherHelper.CheckBeginInvokeOnUI导致我的单元测试失败。这是我在视图模型中最终得到的代码

private void locationChangedHandler(object src, LocationChangedEventArgs e)
{
    if (e.LocationName != this.CurrentPlaceName)
    {
        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);
        }
    }
}

我确实在http://kentb.blogspot.com/2009/04/mvvm-infrastructure-viewmodel.html中尝试了这个技巧,但在单元测试期间调用Dispatcher.CurrentDispatcher上的Invoke失败了,所以它失败了。这就是为什么我直接调用辅助方法,如果运行是在测试而不是应用程序。

这可能不对 - ViewModel不应该关心它的调用位置。任何人都可以看到为什么Kent Boogaart的调度程序方法和MVVM-lite DispatcherHelper.CheckBeginInvokeOnUI都不能在我的单元测试中工作?

3 个答案:

答案 0 :(得分:1)

我这样做:

class MyViewModel() {
    private readonly SynchronizationContext _syncContext;

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

    ...

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

默认上下文将是您的测试中的线程池和UI中的调度程序。如果您想要其他一些行为,也可以轻松创建自己的测试上下文。

答案 1 :(得分:0)

我不相信MVVM-lite中有一个简单的答案。您有正确的调用DispatcherHelper.CheckBeginInvokeOnUI的解决方案。但是,在运行单元测试时,UI不存在,并且DispatcherHelper将无法正常运行。

我使用ReactiveUI。它的版本DispatcherHelper.CheckBeginInvokeOnUI(RxApp.DeferredScheduler)将检查以确定它是否在单元测试中运行。如果是,它将在当前线程上运行,而不是尝试编组到不存在的UI线程。您可以使用此代码构建自己的DispatcherHelper检查。相关代码位于RxApp.cs方法InUnitTestRunner()(第196行)中。它非常hacky,但它有效,我认为没有更好的方法。

答案 2 :(得分:0)

我只是在我的ViewModelUnitTestBase中调用Initialize方法,它工作正常。 确保DispatcherHelper.UIDispatcher不为空。

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();
    }
}