我有一个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都不能在我的单元测试中工作?
答案 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();
}
}