我在MVVM项目中使用Rx和RxUI,并且有一个视图模型,可以异步地从WCF服务查询其数据。在单元测试中,我创建了一个模拟对象,它返回一个具有预期值的Task。
以下是我的视图模型的概述
public class ViewModel : ReactiveObject
{
private IContext _context;
public ViewModel(IContext context)
{
_context = context;
Observable.FromAsync(() => _context.WcfServiceCall())
.Subscribe(result => {
Children.AddRange(results.Select(r => new ChildViewModel(r).ToList()));
});
}
public ObservableCollection<ChildViewModel> { get; private set;}
}
我的单元测试看起来像这样
[TestFixture]
public class ViewModelTest : AssertionHelper
{
[Test]
public void ShouldSetChildren()
{
var c = new Mock<IContext>();
c.Setup(q => q.WcfServiceCall())
.Returns(Task.Run(() => new []{ 1,2,3,4,5,6 })):
var vm = new ViewModel(c.Object);
var p = vm.Children.First(); // this call sometimes fails
...
}
}
我遇到的问题是我有超过400个测试做这种事情,他们大部分时间都在工作但是我随机地得到失败的测试,一次一个或两个,报告序列没有值。这是不可预测和随机的。我可以在失败后再次运行测试并且全部成功。我按照here所述添加了TestScheduler,但问题仍然存在。
有没有更好的方法来测试进行异步方法调用的方法?
编辑保罗贝特的意见: 我看到FromAsync没有采用IScheduler参数,但我确实有SubscribeOn和ObserveOn可用。
或者,我可以直接调用WCF异步方法并将返回的Task转换为observable。我不确定我什么时候更适合使用Observable.FromAsync而不是使用它。
答案 0 :(得分:1)
Observable.FromAsync
是否采用IScheduler
参数?它是你的测试调度程序吗?
答案 1 :(得分:0)
我意识到我的单元测试不正确,因为他们触摸了太多的活动部件。我有测试验证模拟的Web服务调用是在预期时进行的,我真的不应该在不关注该测试点的测试中包含这种复杂性。
导航到视图模型时,我正在调用wcf服务。这是我的视图模型的更好表示,其中有关于指定IScheduler的微小更改:
public class ViewModel : ReactiveObject, IRoutableViewModel
{
private IContext _context;
public ViewModel(IContext context)
{
_context = context;
Weeks = new ReactiveCollection<WeekViewModel>();
this.WhenNavigatedTo(() =>
{
_context.Service.GetWeeksAsync()
.ToObservable()
.ObserveOn(RxApp.DeferredScheduler)
.Subscribe(result =>
{
Weeks.AddRange(result.Select(w => WeekViewModel(w)));
});
});
// ...
}
public ReactiveCollection<WeekViewModel> Weeks { get; private set; }
}
而不是设置上下文来填充Weeks集合,然后使用路由器导航到ViewModel,我现在在单元测试中添加对象到Weeks集合,并跳过导航和异步Web服务调用。这有点减少了测试,消除了我所知道的不稳定性,并缩短了测试套件的执行时间。
所以我的单元测试看起来像这样
[TestFixture]
public class ViewModelTest : AssertionHelper
{
[Test]
public void ShouldSetChildren()
{
var c = new Mock<IContext>();
var vm = new ViewModel(c.Object);
vm.Children.AddRange((new []{1,2,3,4,5,6}).Select(i => new ChildViewModel(i)));
var p = vm.Children.First(); // this is valid now - no timing issues
...
}
}
代码在应用程序中表现正确但在测试运行器中存在问题,所以我相信这解决了我的直接问题。