为此反应性UI方案构建测试(或属性)



我不确定结构此测试的正确方法。我在这里有一个视图模型:

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

依赖关系是AdventureWorks

的数据访问对象
public interface IAdventureWorksRepository
{
  IQueryable<vIndividualCustomer> vIndividualCustomers { get; }
}

最后,我的测试看起来像这样:

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

    
}

因此,ViewModel应立即开始搜索负载

我的直接问题是,即使我正常运行代码时,ISBUSY属性似乎并未设置在测试调度程序中。我是否在视图模型中正确使用toproperty方法?

更一般而言,当我的测试对象具有这样的依赖性时,进行完整的"时间旅行"测试的正确方法是什么?问题是,与我看到的大多数测试示例不同,所谓的界面不可能。这只是一个同步查询,在我的视图模型中使用了异步。当然,在查看模型测试中,我可以嘲笑查询做我想要的任何RX事情。例如,如果我希望查询持续200个滴答,我将如何设置?

所以,您的代码中有几件事可以阻止您使事情正确工作:

不要在ViewModel构造函数中调用命令

首先,在构造函数中调用Execute意味着您永远不会看到状态更改。最好的模式是编写该命令,但不是立即在VM中执行它,然后在视图中:

this.WhenAnyValue(x => x.ViewModel)
    .InvokeCommand(this, x => x.ViewModel.PerformSearchCommand);

在异步操作后移动时钟

好吧,现在我们可以正确地测试状态之前和之后,我们必须意识到,在每次做通常是异步的事情之后,如果我们使用Testscheduler,我们就必须推进调度程序。这意味着,当我们调用命令时,我们应该立即推进时钟:

Assert.IsTrue(vm.PerformSearchCommand.CanExecute(null));
vm.PerformSearchCommand.Execute(null);
sched.AdvanceByMs(10);

没有IObservable

无法测试时间旅行

但是,诀窍是,您的模拟执行代码立即执行,没有延迟,因此您永远不会看到它很忙。它只是返回罐头价值。不幸的是,注入存储库会很难测试,如果您想查看IsBusy切换。

所以,让我们稍微钻一点构造函数:

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

立即设置测试

现在,我们可以设置测试,以替换performSearchCommand的操作,以延迟它:

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

最新更新