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