Rx反应式扩展Observeondispatcher单元测试错误:当前线程没有与之关联的Dispatcher

时间:2013-09-20 10:30:02

标签: wpf unit-testing system.reactive dispatcher observable

我想对包含如下注册的视图模型进行单元测试:

 public SampleViewModel(IUnityContainer container)
    {
...
    Observable.FromEventPattern<PropertyChangedEventArgs>(gridViewModel, "PropertyChanged")
                    .**ObserveOnDispatcher()**
                    .Subscribe(_ => this.Update());
...
}

当我运行单元测试时,它会在到达此代码时告诉我“当前线程没有与之关联的Dispatcher。”。

一种解决方案是使用调度程序,但我不想修改Viewmodel。

是否有解决方案使单元测试通过此语句而不会出现错误?

4 个答案:

答案 0 :(得分:6)

我建议您向IScheduler提供自己的ObserveOn(IScheduler)实施,而不是使用ObserveOnDispatcher()运算符。我使用了加载DispatcherFrameDispatcher的技术,但问题是您仍在使用Dispatcher。最终我发现你只是“从悬崖上掉下来”,特别是一旦你有长时间运行的背景线程。按照“单元测试中没有线程”的指导原则,不要让调度员靠近你的ViewModels!你的单元测试运行得更快,更快。

处理此问题的一个更好的方法是注入一个接口,该接口可以访问Dispatcher Scheduler(通过IScheduler接口)。这允许您在公开TestScheduler的实现中替换。您现在可以控制单元测试的时间。您可以控制和验证为每个调度程序编组的操作。

这是一个非常古老的(前Rx)帖子“单元”测试WPF与2009年初的Dispatcher调用。当时这似乎是一个好主意。

https://leecampbell.com/2009/02/17/responsive-wpf-user-interfaces-part-5/

有关使用Rx进行测试的更多信息以及我在Rx上的其他网站中找到了TestScheduler

http://introtorx.com/Content/v1.0.10621.0/16_TestingRx.html

答案 1 :(得分:2)

这对我有用。 在设置单元测试时,我创建了一个应用程序来模拟我的VM的环境:

static Application App;

static void BeforeTestRun()
{
  var waitForApplicationRun = new ManualResetEventSlim();
  Task.Run(() =>
  {
    App = new Application();
    App.Startup += (s, e) => { waitForApplicationRun.Set(); };
    App.Run();
  });

  waitForApplicationRun.Wait();      
}

这就是我用它来实现视图模型的方法。

App.Dispatcher.Invoke(() => { this.viewModel = new ViewModel(); }); 

答案 2 :(得分:1)

要正确地对您的viewmodel进行单元测试,您确实需要能够提供其所有依赖项。在这种情况下,您的viewmodel依赖于调度程序。使您的viewmodel采用IScheduler依赖关系是理想的方法。但如果你真的不想这样做,那么试着看看这个重复的问题:Unit test IObservable<T> with ObserveOnDispatcher

答案 3 :(得分:0)

我找到了一个避免错误的解决方案,只需从Unit Test代码中使用如下调度程序实例化ViewModel:

SampleViewModel sampleViewModel;
var dispatcher = Application.Current != null ? Application.Current.Dispatcher : Dispatcher.CurrentDispatcher;

 dispatcher.Invoke((Action)(() => sampleViewModel = new SampleViewModel(this.container);

这一切似乎都可以在不修改当前代码的情况下工作,也许还有更好的解决方案。