如何为ReactiveUI中InvokeCommand的命令参数编写单元测试
ReactiveUI响应式管道单元测试问题及解决方案
问题背景
需要编写单元测试验证ReactiveUI响应式管道逻辑,暂未找到正确实现方案。以下为ReactiveUI官方提供的响应式管道示例代码:
this.WhenAnyValue(x => x.SearchQuery) .Throttle(TimeSpan.FromSeconds(0.8), RxApp.TaskpoolScheduler) .Select(query => query?.Trim()) .DistinctUntilChanged() .Where(query => !string.IsNullOrWhiteSpace(query)) .ObserveOn(RxApp.MainThreadScheduler) .InvokeCommand(ExecuteSearch);
原本的实现思路是在单元测试中重写ExecuteSearch命令,对传入的命令参数进行校验,但方案未生效,始终执行原始命令。
原始测试用例代码
[TestFixture] public class TestInvokeCommand { [Test] public void TestPipeline() { new TestScheduler().With(scheduler => { var vm = new ExampleViewModel(); vm.Activator.Activate(); var wasExecuted = false; // 思路:重写ExecuteSearch命令,获取传入参数并做校验 vm.ExecuteSearch = ReactiveCommand.Create<string>(query => { // 这行代码始终不会执行 Assert.That(query, Is.EqualTo("TestQuery")); wasExecuted = true; }); vm.SearchQuery = "TestQuery"; scheduler.AdvanceByMs(3000); Assert.That(wasExecuted, Is.True); // 此处断言失败 }); } // 示例ViewModel的实现 class ExampleViewModel : ReactiveObject, IActivatableViewModel { public ViewModelActivator Activator { get; } = new(); public ExampleViewModel() { this.WhenActivated(disposables => { ExecuteSearch = ReactiveCommand.CreateFromTask<string>(async (queryString, token) => await Task.Delay(100, token)) .DisposeWith(disposables); this.WhenAnyValue(x => x.SearchQuery) .Throttle(TimeSpan.FromSeconds(0.8), RxApp.TaskpoolScheduler) .Select(query => query?.Trim()) .DistinctUntilChanged() .Where(query => !string.IsNullOrWhiteSpace(query)) .ObserveOn(RxApp.MainThreadScheduler) .InvokeCommand(ExecuteSearch) .DisposeWith(disposables); }); } public ReactiveCommandBase<string, System.Reactive.Unit> ExecuteSearch { get; set; } private string _searchQuery; public string SearchQuery { get => _searchQuery; set => this.RaiseAndSetIfChanged(ref _searchQuery, value); } } }
问题原因及修复方案
问题出在ExecuteSearch的属性定义上:它的setter方法没有触发INotifyPropertyChanged通知。InvokeCommand的内部实现依赖target.WhenAnyValue(commandProperty)来监听命令属性的变化,修改ExecuteSearch的属性定义,给setter加上属性变更通知即可让测试正常运行:
private ReactiveCommandBase<string, System.Reactive.Unit> _executeSearch; public ReactiveCommandBase<string, System.Reactive.Unit> ExecuteSearch { get => _executeSearch; set => this.RaiseAndSetIfChanged(ref _executeSearch, value); }
内容的提问来源于stack exchange,提问作者ChristianMurschall
相关产品推荐
相关产品推荐

