迁移后的 Reactive 6.5.0 ReactiveCommand

Reactive 6.5.0 ReactiveCommand after migration

迁移到 ReactiveUI 6.5.0 后,我的测试开始失败。 Resharper Test 运行ner 的行为非常奇怪。当我 运行 一个接一个地进行测试时 - 测试通过了,但如果我 运行 他们采用大爆炸方法 - 运行 单元测试(我们有大约 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 returns _runProcessCommand.

此测试已通过 ReactiveUI 4.5。 是否保证同步执行?如果不是 - 现在测试它的正确方法是什么?为什么 运行 一个接一个地表现不同?我很乐意提出任何想法。

编辑:

我注意到 运行Impl 正在不同的线程上调用(仅当 运行 进行多个单元测试时)并且如果我替换 [=26= 它不会改变任何东西]

_runProcessCommand.Subscribe(RunImpl);

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

运行Impl 仍在另一个线程上调用,因此在执行命令之前进行断言。

编辑 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);

当为单个测试调用调度程序工厂时,它抛出异常并 returning CurrentThreadScheduler.Instance。 当 运行 多次测试 Dispatcher.Current return 一个实例。谁能给我解释一下这是怎么回事?

简单的可重现测试:

    [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 到 return 不同值的原因。之前的测试之一是使用 DelegateCommand,它在内部调用 CommandManager.InvalidateRequerySuggested(); 创建调度程序,值由 attemptToCreateScheduler() 编辑 return 导致其余测试失败,因为它们没有在 Immediate Scheduler 上调用但是调度程序(它在单元测试中是什么?它的行为不像立即调度程序)

编辑 3:

这种行为我在工作中遇到过,但我无法在家里重现。但我确信一定有问题,除非我对 Reactive 有什么不了解的地方。我认为没有必要包括整个项目,这个例子准确地表明调度程序是调度程序而不是当前线程,如果你使用那个函数。请调试它,您会注意到在 WaitForDispatcherScheduler class 中的 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);
        }

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

在幕后 Execute 只是运行 ExecuteAsync().Subscribe(),所以这将做的只是 start 命令 运行,不能保证该命令将在 returns 时完成执行。它在某些测试中所做的事实完全取决于使用的调度程序。

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

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

    await command.ExecuteAsync();

    // assert
}

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