MistyK
MistyK

Reputation: 6222

Reactive 6.5.0 ReactiveCommand after migration

After migrating to ReactiveUI 6.5.0 my tests started failing. The behaviour of Resharper Test Runner is very strange. When I'm running tests one after another - the tests pass but if I run them with big bang approach - Run Unit Tests (we've got about 1k Unit Tests) some tests are failing. This is one of them:

        [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.

This test was passing with ReactiveUI 4.5. Is it guaranteed to be executed synchronously? If not - what is the proper way to test it now? Why is there are different behaviour between running them one after another? I would be glad for any ideas.

Edit:

I've noticed that RunImpl is being invoked on a different thread (only when running multiple unit tests) and it doesn't change anything if I replace

_runProcessCommand.Subscribe(RunImpl);

with

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

RunImpl is still being invoked on a different thread so assert is made BEFORE command is executed.

Edit 2: This is the fix which I invoke in NUnit [SetUp] function. If I observe on Scheduler.Immediate it won't work as well. Any ideas why it's not set to Immediate by default when I run multiple tests? (It doesn't work for all the failing tests)

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

Edit: After deeper debugging into RX sources in WaitForDispatcherScheduler I've noticed that:

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

When invoking scheduler factory for single test it's throwing an exception and returning CurrentThreadScheduler.Instance. When running multiple tests Dispatcher.Current returns an instance. Can anyone explain me what is going here?

Simple reproducable test:

    [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.
    }

Edit 2: I have found what is causing DispatcherScheduler.Current to return different values. One of the previous test is using DelegateCommand which inside calls CommandManager.InvalidateRequerySuggested(); which creates dispatcher and value is returned by attemptToCreateScheduler() causing the rest of the tests failing because they are not called on Immediate Scheduler but Dispatcher (what is it in unit tests? It doesn't behave like immediate scheduler)

Edit3:

This behavior I've experienced at work and I couldn't reproduce it at home. But I am sure that something must be wrong unless there is something I don't understand about Reactive. I think there is no point of including the entire project, this example is showing exactly that scheduler is dispatcher instead of current thread if you use that function. Please debug it and you'll notice a different behaviour between these two calls in attemptToCreateScheduler() function in WaitForDispatcherScheduler class. ReactiveUI is version 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);
        }

Upvotes: 2

Views: 378

Answers (1)

Charles Mager
Charles Mager

Reputation: 26213

There is a mismatch between the asynchronous nature of executing a command and the synchronous test.

Under the covers Execute just runs ExecuteAsync().Subscribe(), so all this will do is start the command running, there's no guarantees that the command will have finished executing by the time it returns. The fact it does in certain tests is entirely dependent on the schedulers in use.

What you should be doing is asynchronously waiting for the command to finish before asserting any side effects:

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

    await command.ExecuteAsync();

    // assert
}

As an aside, and as noted in the various comments, I don't think the intention is for MainThreadScheduler to use the dispatcher in the unit test scenario. It does appear this may be a bug. See this github issue for further detail.

Upvotes: 1

Related Questions