我不确定构建此测试的正确方法.我在这里有一个视图模型:
public class ViewModel { public ReactiveCommand PerformSearchCommand { get; private set; } private readonly ObservableAsPropertyHelper_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 { IQueryablevIndividualCustomers { get; } }
最后,我的测试看起来像这样:
[TestMethod] public void TestTiming() { new TestScheduler().With(sched => { var repoMock = new Mock(); 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方法吗?
更一般地说,当我的测试对象具有这样的依赖性时,进行完整的"时间旅行"测试的正确方法是什么?问题是,与我看到的大多数测试示例不同,被调用的接口不是IObservable.它只是一个同步查询,在我的视图模型中异步使用.当然在视图模型测试中,我可以模拟查询来做任何我需要的东西.例如,如果我希望查询持续200个刻度,我该如何设置?
所以,你的代码中有一些东西阻止你让事情正常工作:
首先,调用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);
然而,诀窍是,你的模拟立即执行代码,没有延迟,所以你永远不会看到它忙.它只返回一个罐装值.不幸的是,如果要查看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); });