【问题标题】:How to Unit Test DelegateCommand that calls async methods in MVVM如何对调用 MVVM 中的异步方法的 DelegateCommand 进行单元测试
【发布时间】:2013-03-21 14:05:34
【问题描述】:

我是单元测试 MVVM 的新手,并在我的项目中使用 PRISM。我正在我们当前的项目上实施单元测试,并且没有运气在线找到资源来告诉我如何测试调用异步方法的 DelegateCommand。这是我的帖子的后续问题 - How to Unit Test a ViewModel with async method. 关于如何在 MVVM 中对异步方法进行单元测试,并回答说可以使用异步 TestMethod 测试公共方法。仅当我要测试的方法是公共方法时,此方案才有效。

问题是我想测试我的 DelegateCommand,因为这是我想在其他类上公开的唯一公开细节,而其他一切都是私有的。我可以公开我的私有方法,但我永远不会这样做,因为它是一个糟糕的设计。我不确定如何解决这个问题 - 是否需要测试 DelegateCommand,或者还有其他一些工作可以解决这个问题?我很想知道其他人如何解决这个问题并以某种方式引导我走向正确的道路。

这是我的代码

 async void GetTasksAsync()
        {
            this.SimpleTasks.Clear();
            Func<IList<ISimpleTask>> taskAction = () =>
                {
                    var result = this.dataService.GetTasks();
                    if (token.IsCancellationRequested)
                        return null;
                    return result;
                };
            IsBusyTreeView = true;

            Task<IList<ISimpleTask>> getTasksTask = Task<IList<ISimpleTask>>.Factory.StartNew(taskAction, token);
            var l = await getTasksTask;          // waits for getTasksTask


            if (l != null)
            {
                foreach (ISimpleTask t in l)
                {
                    this.SimpleTasks.Add(t); // adds to ViewModel.SimpleTask
                }
            }
        }

这也是我的虚拟机中调用上述异步方法的命令

  this.GetTasksCommand = new DelegateCommand(this.GetTasks);
      void GetTasks()
        {
                GetTasksAsync();
        }

现在我的测试方法是这样的

 [TestMethod]
        public void Command_Test_GetTasksCommand()
        {
          MyViewModel.GetTaskCommand.Execute(); // this should populate ViewModel.SimpleTask 
          Assert.IsTrue(MyBiewModel.SimpleTask != null)
        } 

目前我得到的是我的 ViewModel.SimpleTask = null 这是因为它不等待异步方法完成。

【问题讨论】:

    标签: c# unit-testing mvvm prism delegatecommand


    【解决方案1】:

    由于我无法添加 cmets,为了完整起见,您可以在 PRISM 6 中尝试:

    ParsingCommand = new DelegateCommand<string>(async (x) => await StartParsing(x));
    

    【讨论】:

    • 这不能回答问题。问题是如何测试执行async 处理程序的DelegateCommand,而不是如何创建这样的命令。
    【解决方案2】:

    在 Prism 6 中,您可以从异步处理程序创建 DelegateCommandDelegateCommand&lt;T&gt;

    例如:

    startParsingCommand=DelegateCommand .FromAsyncHandler(StartParsingAsync,CanStartParsing) .ObservesProperty(()=> IsParserStarted);

    【讨论】:

    • FromAsyncHandler 在 Prism 6.x 中已过时(我不知道哪个 x :D ),所以有更好的方法吗?
    【解决方案3】:

    我编写了一个 AsyncCommand 类,它从 Execute 方法返回一个 Task 对象。然后,您需要显式地实现 ICommand.Execute,等待来自您的 Execute 实现的任务:

    public class AsyncCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;
    
        public Func<Task> ExecutedHandler { get; private set; }
    
        public Func<bool> CanExecuteHandler { get; private set; }
    
        public AsyncCommand(Func<Task> executedHandler, Func<bool> canExecuteHandler = null)
        {
            if (executedHandler == null)
            {
                throw new ArgumentNullException("executedHandler");
            }
    
            this.ExecutedHandler = executedHandler;
            this.CanExecuteHandler = canExecuteHandler;
        }
    
        public Task Execute()
        {
            return this.ExecutedHandler();
        }
    
        public bool CanExecute()
        {
            return this.CanExecuteHandler == null || this.CanExecuteHandler();
        }
    
        public void RaiseCanExecuteChanged()
        {
            if (this.CanExecuteChanged != null)
            {
                this.CanExecuteChanged(this, new EventArgs());
            }
        }
    
        bool ICommand.CanExecute(object parameter)
        {
            return this.CanExecute();
        }
    
        async void ICommand.Execute(object parameter)
        {
            await this.Execute();
        }
    }
    

    然后您可以将异步任务返回方法传递给命令类:

    public class ViewModel
    {
        public AsyncCommand AsyncCommand { get; private set; }
    
        public bool Executed { get; private set; }
    
        public ViewModel()
        {
            Executed = false;
            AsyncCommand = new AsyncCommand(Execute);
        }
    
        private async Task Execute()
        {
            await(Task.Delay(1000));
            Executed = true;
        }
    }
    

    在您的单元测试中,您只需等待Execute 方法:

    [TestMethod]
    public async Task TestAsyncCommand()
    {
        var viewModel = new ViewModel();
    
        Assert.IsFalse(viewModel.Executed);
        await viewModel.AsyncCommand.Execute();
    
        Assert.IsTrue(viewModel.Executed);
    }
    

    另一方面,UI 将调用显式实现的 ICommand.Execute 方法,该方法负责等待任务。

    (*) 同时我注意到,如果您遵循常见的命名约定,则返回任务的方法实际上应该命名为ExecuteAsync

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-15
      • 1970-01-01
      • 2013-01-27
      • 2023-03-06
      相关资源
      最近更新 更多