使用FromAsyncPattern进行单元测试

And*_*son 2 .net c# wpf unit-testing system.reactive

Reactive Extensions有一个性感的小钩子来简化调用异步方法:

var func = Observable.FromAsyncPattern<InType, OutType>(
    myWcfService.BeginDoStuff,
    myWcfService.EndDoStuff);

func(inData).ObserveOnDispatcher().Subscribe(x => Foo(x));
Run Code Online (Sandbox Code Playgroud)

我在WPF项目中使用它,它在运行时运行良好.

不幸的是,当尝试使用这种技术的单元测试方法时,我遇到了随机故障.包含此代码的测试的每五次执行中约有3次失败.

这是一个示例测试(使用Rhino/unity自动模拟容器实现):

[TestMethod()]
public void SomeTest()
{
   // arrange
   var container = GetAutoMockingContainer();

   container.Resolve<IMyWcfServiceClient>()
      .Expect(x => x.BeginDoStuff(null, null, null))
      .IgnoreArguments()
      .Do(
         new Func<Specification, AsyncCallback, object, IAsyncResult>((inData, asyncCallback, state) =>
            {
               return new CompletedAsyncResult(asyncCallback, state);
             }));

   container.Resolve<IRepositoryServiceClient>()
      .Expect(x => x.EndDoStuff(null))
      .IgnoreArguments()
      .Do(
         new Func<IAsyncResult, OutData>((ar) =>
         {
            return someMockData;
         }));

   // act
   var target = CreateTestSubject(container);

   target.DoMethodThatInvokesService();

   // Run the dispatcher for everything over background priority
   Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new Action(() => { }));

   // assert
   Assert.IsTrue(my operation ran as expected);
}
Run Code Online (Sandbox Code Playgroud)

我看到的问题是,我指定在异步操作完成时运行的代码(在本例中为Foo(x))永远不会被调用.我可以通过在Foo中设置断点并观察它们永远不会到达来验证这一点.此外,我可以在调用DoMethodThatInvokesService(启动异步调用)后强制延迟一段时间,但代码仍然没有运行.我知道的代码调用的Rx框架的线被称为.

我试过的其他事情:

这将我的失败率提高到了五分之一,但它们仍然存在.

  • 我已经重写了Rx代码以使用普通的jane异步模式.这是有效的,但是我的开发者自我真的很想使用Rx而不是厌倦旧的开始/结束.

最后我手边有一个工作(即不要使用Rx),但我觉得它并不理想.如果有人在过去遇到这个问题并找到了解决方案,我非常希望能听到它.

更新:

我还发布了Rx论坛,他们将包括一个即将发布的测试调度程序.一旦可用,这可能是最终的解决方案.

Ana*_*tts 6

问题是MSTest.exe运行Dispatcher(即Dispatcher.Current!= null),因此ObserveOnDispatcher工作.但是,这个Dispatcher什么都不做!(即将忽略排队的调度程序项)您编写的明确使用Schedule.Dispatcher的任何代码都是不可测试的

我在ReactiveUI中通过暴力解决了这个问题- 这是重要的一点:

https://github.com/reactiveui/ReactiveUI/blob/master/ReactiveUI/RxApp.cs#L99

我们基本上设置了一个定义默认调度程序的全局变量,然后我们尝试检测我们何时进入测试运行器.

然后,在我们实现IObservable的类中,都采用IScheduler参数,其默认值最终将成为全局默认调度程序.我可能做得更好,但这对我有用,并使ViewModel代码再次可测试.