TestScheduler下单元测试在Observable.Interval订阅任务完成前结束
解决方案:让异步操作纳入TestScheduler管控
你的问题核心是异步任务的执行脱离了TestScheduler的控制——虽然你用TestScheduler触发了Interval,但Select(async _ => ...)里的异步方法是在ThreadPool线程上执行的,TestScheduler无法管控这些线程的执行时机,导致断言时异步任务可能还没跑完。
下面是两种优雅的解决思路:
思路1:改造生产代码,让调度器可注入并管控异步操作
把生产代码里的调度器改为可注入,同时用Observable.FromAsync替代直接在Select里写异步lambda,这样能把异步操作绑定到指定的调度器上:
生产代码调整
// 构造函数注入调度器,生产环境传入Scheduler.ThreadPool public YourClass(IScheduler scheduler) { _scheduler = scheduler; _myInterval = Observable.Interval(TimeSpan.FromSeconds(10), _scheduler); _subscription = _myInterval // 用FromAsync将异步方法包装为Observable,并指定调度器 .Select(_ => Observable.FromAsync(FetchSomeValueFromAsyncService, _scheduler)) .Switch() .Subscribe(UseValueInAnotherFunction); }
测试代码调整
测试时传入TestScheduler,触发间隔后,让TestScheduler执行完所有待处理的任务,再做断言:
// 触发第一个间隔 _scheduler.AdvanceBy(TimeSpan.FromSeconds(10).Ticks); // 让TestScheduler执行完所有异步任务 _scheduler.RunToCompletion(); // 此时断言就能保证值已更新 _myClass.Result.Should().Be(2);
思路2:不改动生产代码,在测试层管控异步任务的延续
如果不想修改生产代码,可以在Mock异步服务时,把任务的延续强制绑定到TestScheduler上:
测试Mock调整
// 模拟异步方法时,让任务的延续在TestScheduler上执行 _mockAsyncService .Setup(s => s.FetchSomeValueFromAsyncService()) .Returns(Task.FromResult(2).ContinueWith(t => t.Result, _scheduler));
测试执行调整
同样在触发间隔后,让TestScheduler跑完所有任务:
_scheduler.AdvanceBy(TimeSpan.FromSeconds(10).Ticks); _scheduler.RunToCompletion(); _myClass.Result.Should().Be(2);
为什么这两种方法有效?
TestScheduler只能管控绑定到它上面的任务执行,之前的异步操作在ThreadPool线程上跑,TestScheduler管不到。通过以上两种方式,把异步任务的执行/延续都绑定到TestScheduler,就能通过RunToCompletion()确保所有任务都执行完毕,断言时自然能拿到更新后的值,完全不需要依赖Task.Delay这种不可靠的等待。
内容的提问来源于stack exchange,提问作者GooseZA
相关产品推荐
相关产品推荐

