【问题标题】:Using Dispatcher in unit-testable MVVM code在可单元测试的 MVVM 代码中使用 Dispatcher
【发布时间】:2011-01-24 03:36:25
【问题描述】:

我有一个 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 在我的单元测试中都不起作用?

【问题讨论】:

    标签: multithreading mvvm mvvm-light dispatcher


    【解决方案1】:

    我是这样做的:

    class MyViewModel() {
        private readonly SynchronizationContext _syncContext;
    
        public MyViewModel() {
            _syncContext = SynchronizationContext.Current; // or use DI
        )
    
        ...
    
        public void SomeTimerEvent() {
            _syncContext.Post(_ => UpdateUi(), null);
        }
    }
    

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        【解决方案3】:

        我只是在 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();
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-11-09
          • 1970-01-01
          • 1970-01-01
          • 2014-07-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-06
          相关资源
          最近更新 更多