迁移后的Reactive 6.5.0 ReactiveCommand

时间:2016-05-11 07:20:59

标签: c# wpf unit-testing system.reactive reactiveui

迁移到ReactiveUI 6.5.0后,我的测试开始失败。 Resharper Test Runner的行为非常奇怪。当我一个接一个地运行测试时 - 测试通过但是如果我用大爆炸方法运行它们 - 运行单元测试(我们有大约1k单元测试)一些测试失败了。这是其中之一:

        [Test]
        public void TestThat_CallingRun_CallsLogin()
        {
            //act
            _sut.Actions.Single().Command.Execute(null);

            //assert
            _controller.AssertWasCalled(x => x.Login());
        }

        public ViewModel(IWfController workflowController)
        {
            _workflowController = workflowController;

            _runProcessCommand = ReactiveCommand.Create(CanRunProcess());
            _runProcessCommand.Subscribe(RunImpl);
        }

        private void RunImpl(object obj)
        {
            _workflowController.Login();
        }

_sut.Actions.Single().Command返回_runProcessCommand

此测试通过了ReactiveUI 4.5。 是否保证同步执行?如果不是 - 现在测试它的正确方法是什么?为什么一个接一个地运行它们之间会有不同的行为?我会很高兴任何想法。

编辑:

我注意到RunImpl是在另一个线程上调用的(仅在运行多个单元测试时),如果我替换它就不会改变任何东西

_runProcessCommand.Subscribe(RunImpl);

_runProcessCommand.ObserveOn(RxApp.MainThreadScheduler).Subscribe(RunImpl)

仍在另一个线程上调用RunImpl,因此在执行BEFORE命令时会执行assert。

编辑2: 这是我在NUnit [SetUp]函数中调用的修复程序。如果我在Scheduler.Immediate上观察它也不会有效。我运行多个测试时默认情况下没有设置为立即的任何想法? (它不适用于所有失败的测试)

 RxApp.MainThreadScheduler = Scheduler.Immediate;
 RxApp.TaskpoolScheduler = TaskPoolScheduler.Default;

编辑: 在WaitForDispatcherScheduler中对RX源进行更深入的调试后,我注意到了:

IScheduler attemptToCreateScheduler()
        {
            if (_innerScheduler != null) return _innerScheduler;
            try {
                _innerScheduler = _schedulerFactory();
                return _innerScheduler;
            } catch (Exception) {
                // NB: Dispatcher's not ready yet. Keep using CurrentThread
                return CurrentThreadScheduler.Instance;
            }
        }
 RxApp.MainThreadScheduler = new WaitForDispatcherScheduler(() => DispatcherScheduler.Current);

在为单个测试调用调度程序工厂时,它会抛出异常并返回CurrentThreadScheduler.Instance。 运行多个测试时Dispatcher.Current返回一个实例。谁能解释一下这里发生了什么?

简单的可重复测试:

    [Test]
    public void Test1()
    {
         var disp =DispatcherScheduler.Current; // throws exception (System.InvalidOperationException : The current thread has no Dispatcher associated with it.) when         running single tests, not throwing exception when running multiple tests.
    }

编辑2: 我找到了导致DispatcherScheduler.Current返回不同值的原因。之前的测试之一是使用DelegateCommand,其内部调用CommandManager.InvalidateRequerySuggested();创建调度程序,值由attemptToCreateScheduler()返回,导致其余测试失败,因为它们未在Immediate Scheduler上调用,而是Dispatcher(它是什么?在单元测试中?它的行为不像立即调度程序)

EDIT3:

我在工作中经历过的这种行为,我无法在家中重现它。但我确信,除非有一些我对Reactive不了解的东西,否则一定有问题。我认为没有必要包括整个项目,这个例子正好显示调度程序是调度程序而不是当前线程,如果你使用该函数。请调试它,你会发现在WaitForDispatcherScheduler类的attemptToCreateScheduler()函数中这两个调用之间有不同的行为。 ReactiveUI是7.0版。

  [Test]
        public void TestMethod1()
        {
            RxApp.MainThreadScheduler.Schedule<string>(null, (x, y) => Disposable.Empty);
            CommandManager.InvalidateRequerySuggested();
            RxApp.MainThreadScheduler.Schedule<string>(null, (x, y) => Disposable.Empty);
        }

1 个答案:

答案 0 :(得分:1)

执行命令的异步性质与同步测试之间存在不匹配。

在封面Execute下只运行ExecuteAsync().Subscribe(),所以这一切都是 start 命令运行,无法保证命令将在时间内完成执行它返回。它在某些测试中所做的事实完全取决于使用的调度程序。

你应该做的是在断言任何副作用之前异步等待命令完成:

[Test]
public async Task Test()
{
    // arrange

    await command.ExecuteAsync();

    // assert
}

顺便说一句,正如各种评论所述,我认为MainThreadScheduler的目的不是在单元测试场景中使用调度程序。看来这可能是一个错误。有关详细信息,请参阅this github issue