1
votes

I'm having trouble testing ReactiveCommands and their ability to execute.

Given the following View Model:

    public class HowToTestViewModel : ReactiveObject
    {
        public ReactiveCommand<Unit> CommandThatDoesStuff { get; set; }
        public IObservable<bool> CanExecute { get; set; }

        public ObservableCollection<string> TheCollectionMustHaveItems { get; set; }

        public HowToTestViewModel()
        {
            TheCollectionMustHaveItems = new ObservableCollection<string>();

            this.CanExecute = this.WhenAnyValue(vm => vm.TheCollectionMustHaveItems)
                .Do(debug => Console.WriteLine("Can Execute Value: " + debug.Count()))
                .Where(col => col.Any())
                .Do(debug => Console.WriteLine("Can Execute (not empty?) Value: " + debug.Count()))
                .Select(x => true);

            CommandThatDoesStuff = ReactiveCommand.CreateAsyncTask(this.CanExecute, async param => {
                //web service or something...
                await Task.Delay(1000);
            });

            CommandThatDoesStuff.Subscribe(next => Console.WriteLine("The command fired and no exceptions were thrown."));

            CommandThatDoesStuff.ThrownExceptions.Subscribe(error => Console.WriteLine("boom. handle your business here."));

        }
    }

I want to test to ensure that the command can't be executed unless TheCollectionMustHaveItems is not null and has items.

    [Test]
    public void CanExecute_spec()
    {
        (new TestScheduler()).With(sched =>
        {
            var sut = new HowToTestViewModel();

            sut.CommandThatDoesStuff.Execute(null);

            sut.CommandThatDoesStuff.CanExecute(null).Should().BeFalse();

            sut.TheCollectionMustHaveItems.Should().BeEmpty();

            sut.TheCollectionMustHaveItems.Add("item added"); //CanExecute should update

            sched.AdvanceBy(1000);

            sut.CommandThatDoesStuff.CanExecute(null).Should().BeTrue();

        });
    }

I'm using the TestScheduler, but I don't exactly know why. My question is:

  • How do I get the test to pass?
  • Should I be testing the CanExecute on the command?
  • Should I be using ToPropery and ObservableAsPropertyHelper rather than a public IObservable for CanExecute?

Update 1

Following the suggestions below I changed my ObservableCollection to a ReactiveList and I (think) I am using the TheCollectionMustHaveItems.CountChanged to validate if the CommandThatDoesStuff can execute. Using the following updated VM and Test I get the same error.

    public class HowToTestViewModel : ReactiveObject
    {
         ....

        public IReactiveList<string> TheCollectionMustHaveItems { get; set; }

        public HowToTestViewModel()
        {
            TheCollectionMustHaveItems = new ReactiveList<string>();

            this.CanExecute = this
                .TheCollectionMustHaveItems.CountChanged
                .Do(next => Console.WriteLine("logging the next result:" + next))
                .ObserveOn(RxApp.MainThreadScheduler)
                .Where(collection => TheCollectionMustHaveItems.Any())
                .Select(x => true);

        ...

        }
    }

Update 2

Updating the test to await the CommandThatDoesStuff causes the test runner to never return.

    [Test]
        public async void CanExecute_spec()
        {
            await (new TestScheduler()).With(async sched =>
             {
             var sut = new HowToTestViewModel();

             await sut.CommandThatDoesStuff.ExecuteAsyncTask(null);

             sut.CommandThatDoesStuff.CanExecute(null).Should().BeFalse();

             sut.TheCollectionMustHaveItems.Should().BeEmpty();

             sut.TheCollectionMustHaveItems.Add("item added"); //CanExecute should update

             sut.CanExecute.Subscribe(next => next.Should().BeTrue());

             sched.AdvanceBy(1000);

             sut.CommandThatDoesStuff.CanExecuteObservable.Subscribe(next => next.Should().BeTrue());
         });
    }
2

2 Answers

2
votes

WhenAnyValue doesn't work with an ObservableCollection

Your best bet is to use a ReactiveList that comes with ReactiveUI and observe the CountChanged property.

1
votes

First, because you are using a .Where followed by a .Select, your CanExecute will only ever return true values. Change it to this:

this.CanExecute = this
    .TheCollectionMustHaveItems.CountChanged
    .Log(this, "next result")   // RxUI has a nice Log extension method that can replace your previous usage of Do
    .ObserveOn(RxApp.MainThreadScheduler)
    .Select(count => count > 0);

Secondly, async test return types should be Task, not void.