我不确定构建此测试的正确方法。我在这里有一个视图模型:
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方法?
更一般地说,当我的测试对象具有这样的依赖性时,进行完整的'时间旅行'测试的正确方法是什么?问题是,与我看到的大多数测试示例不同,被调用的接口不是IObservable。它只是一个同步查询,在我的视图模型中异步使用。当然在视图模型测试中,我可以模拟查询来做任何我需要的东西。如果我想让查询持续200个刻度,我该怎么设置呢?
答案 0 :(得分:12)
所以,你的代码中有一些东西阻止你让事情正常工作:
首先,在构造函数中调用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);
});