I'm not sure the correct way to structure this test. I've got a view model here:
public class ViewModel
{
public ReactiveCommand PerformSearchCommand { get; private set; }
private readonly ObservableAsPropertyHelper<bool> _IsBusy;
public bool IsBusy
{
get { return _IsBusy.Value; }
}
public ViewModel(IAdventureWorksRepository _awRepository)
{
PerformSearchCommand = new ReactiveCommand();
PerformSearchCommand.RegisterAsyncFunction((x) =>
{
return _awRepository.vIndividualCustomers.Take(1000).ToList();
}).Subscribe(rval =>
{
CustomerList = rval;
SelectedCustomer = CustomerList.FirstOrDefault();
});
PerformSearchCommand.IsExecuting.ToProperty(this, x => x.IsBusy, out _IsBusy);
PerformSearchCommand.Execute(null); // begin executing immediately
}
}
The dependency is a data access object to AdventureWorks
public interface IAdventureWorksRepository
{
IQueryable<vIndividualCustomer> vIndividualCustomers { get; }
}
Finally, my test looks something like this:
[TestMethod]
public void TestTiming()
{
new TestScheduler().With(sched =>
{
var repoMock = new Mock<IAdventureWorksRepository>();
repoMock.Setup(x => x.vIndividualCustomers).Returns(() =>
{
return new vIndividualCustomer[] {
new vIndividualCustomer { FirstName = "John", LastName = "Doe" }
};
});
var vm = new ViewModel(repoMock.Object);
Assert.AreEqual(true, vm.IsBusy); //fails?
Assert.AreEqual(1, vm.CustomerList.Count); //also fails, so it's not like the whole thing ran already
sched.AdvanceTo(2);
Assert.AreEqual(1, vm.CustomerList.Count); // success
// now the customer list is set at tick 2 (not at 1?)
// IsBusy was NEVER true.
});
}
So the viewmodel should immediately begin searching upon load
My immediate problem is that the IsBusy property doesn't seem to get set in the testing scheduler, even though it seems to work fine when I run the code normally. Am I using the ToProperty method correctly in the view model?
More generally, what is the proper way to do the full 'time travel' testing when my object under test has a dependency like this? The issue is that unlike most testing examples I'm seeing, the called interface is not an IObservable. It's just a synchronous query, used asynchronously in my view model. Of course in the view model test, I can mock the query to do whatever rx things I want. How would I set this up if I wanted the query to last 200 ticks, for example?
So, you've got a few things in your code that is stopping you from getting things to work correctly:
First, calling Execute
in the constructor means you'll never see the state change. The best pattern is to write that command but not execute it in the VM immediately, then in the View:
this.WhenAnyValue(x => x.ViewModel)
.InvokeCommand(this, x => x.ViewModel.PerformSearchCommand);
Ok, now that we can properly test the before and after state, we have to realize that after every time we do something that normally would be async, we have to advance the scheduler if we use TestScheduler. This means, that when we invoke the command, we should immediately advance the clock:
Assert.IsTrue(vm.PerformSearchCommand.CanExecute(null));
vm.PerformSearchCommand.Execute(null);
sched.AdvanceByMs(10);
However, the trick is, your mock executes code immediately, there's no delay, so you'll never see it be busy. It just returns a canned value. Unfortunately, injecting the Repository makes this difficult to test if you want to see IsBusy
toggle.
So, let's rig the constructor a little bit:
public ViewModel(IAdventureWorksRepository _awRepository, Func<IObservable<List<Customer>>> searchCommand = null)
{
PerformSearchCommand = new ReactiveCommand();
searchCommand = searchCommand ?? () => Observable.Start(() => {
return _awRepository.vIndividualCustomers.Take(1000).ToList();
}, RxApp.TaskPoolScheduler);
PerformSearchCommand.RegisterAsync(searchCommand)
.Subscribe(rval => {
CustomerList = rval;
SelectedCustomer = CustomerList.FirstOrDefault();
});
PerformSearchCommand.IsExecuting
.ToProperty(this, x => x.IsBusy, out _IsBusy);
}
Now, we can set up the test, to replace PerformSearchCommand's action with something that has a delay on it:
new TestScheduler().With(sched =>
{
var repoMock = new Mock<IAdventureWorksRepository>();
var vm = new ViewModel(repoMock.Object, () =>
Observable.Return(new[] { new vIndividualCustomer(), })
.Delay(TimeSpan.FromSeconds(1.0), sched));
Assert.AreEqual(false, vm.IsBusy);
Assert.AreEqual(0, vm.CustomerList.Count);
vm.PerformSearchCommand.Execute(null);
sched.AdvanceByMs(10);
// We should be busy, we haven't finished yet - no customers
Assert.AreEqual(true, vm.IsBusy);
Assert.AreEqual(0, vm.CustomerList.Count);
// Skip ahead to after we've returned the customer
sched.AdvanceByMs(1000);
Assert.AreEqual(false, vm.IsBusy);
Assert.AreEqual(1, vm.CustomerList.Count);
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With