在可单元测试的 MVVM 代码中使用 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-infrastruct-viewmodel.html 但是在单元测试期间对 Dispatcher.CurrentDispatcher 上的 Invoke 的调用未能运行,因此失败。这就是为什么如果运行是在测试而不是应用程序中,我会直接调用帮助器方法。
这是不对的——ViewModel 不应该关心它是从哪里调用的。谁能明白为什么 Kent Boogaart 的调度程序方法和 MVVM-lite DispatcherHelper.CheckBeginInvokeOnUI 在我的单元测试中都不起作用?
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.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);
}
}
}
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我这样做:
默认上下文将是测试中的线程池和 UI 中的调度程序。如果您想要一些其他行为,您还可以轻松创建自己的测试上下文。
I do it like this:
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.
我不相信 MVVM-lite 中有一个简单的答案。您有调用 DispatcherHelper.CheckBeginInvokeOnUI 的正确解决方案。但是,当单元测试运行时,UI不存在,DispatcherHelper将无法正常运行。
我使用 ReactiveUI。它的 DispatcherHelper.CheckBeginInvokeOnUI (RxApp.DeferredScheduler) 版本将检查以确定它是否正在单元测试中运行。如果是,它将在当前线程上运行,而不是尝试编组到不存在的 UI 线程。您也许可以使用此代码在 DispatcherHelper 中构建您自己的检查。相关代码位于 RxApp.cs 方法 InUnitTestRunner() (第 196 行)。这是相当老套的,但它有效,我不认为有更好的方法。
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.
我只是在 ViewModelUnitTestBase 中调用 Initialize 方法,它工作正常。
确保 DispatcherHelper.UIDispatcher 不为 null。
I just call the Initialize method in my ViewModelUnitTestBase and it works fine.
Make sure that the DispatcherHelper.UIDispatcher is not null.