Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Structuring tests (or property) for this reactive ui scenario

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?

like image 232
Clyde Avatar asked Feb 05 '14 21:02

Clyde


1 Answers

So, you've got a few things in your code that is stopping you from getting things to work correctly:

Don't invoke commands in ViewModel Constructors

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

Move the clock after async actions

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

Can't test Time Travel without IObservable

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

Set up the test now

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);
});
like image 91
Ana Betts Avatar answered Oct 25 '22 10:10

Ana Betts