如何对包含异步方法的ViewModel进行单元测试。

5

我不确定从哪里开始,但让我简单介绍一下我的情况和我想要实现的目标。我对MVVM上的单元测试相当陌生,并且在测试使用PRISM委托命令属性公开的命令时遇到了困难。

我的委托命令调用异步方法,必须等待才能获取实际结果。以下是由我想要测试的方法调用的异步方法。

 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,这是因为它没有等待异步方法完成。我知道在stackoverflow上有一些相关主题,但我找不到与我的DelegateCommands相关的内容。

1个回答

10
你的GetTasksAsync方法应该返回一个Task,这样你才能等待它。
我推荐在Channel9上查看这个系列,特别是这一集解释了你的问题。
只是为了说明:简单地将GetTasksAsync的签名更改为
Task GetTasksAsync();

允许您这样做:

var t = GetAsync();
t.Wait();
Assert(...);

如果您想在单元测试中测试命令而不是实际调用的方法,可以使用ViewModel中的字段来存储待等待的任务(不太干净),或者将DelegateCommand替换为像Awaitable DelegateCommand中描述的东西。
更新:除了博客文章之外,考虑到您正在使用PRISM,您应该看看Project Kona,它来自与PRISM相同团队。他们实际上实现了DelegateCommand以支持AsyncHandlers

3
甚至更好的是,这使您可以使用“async Task”单元测试和执行“await GetTasksAsync()”。 - Stephen Cleary
感谢您的回答。然而,GetTasksAsync()是我的ViewModel中的私有方法,不想将其暴露给项目中的任何其他对象,并且仅由委托调用。话虽如此,我只测试公共方法。这些委托是公开的。引入另一个字段可能是一种方法,但您是正确的,它会很混乱。我将查看您发送给我的Awaitable Delegate Command链接,看看它是否有用。 - Jepoy_D_Learner
更新:我已经实现了您上面的答案,因为我还没有找到其他解决方案的替代方法-例如Awaitable DelegateCommand。实施这意味着我将我的私有方法更改为公共方法(这很奇怪!),现在我的问题是当使用t.Wait()调用GetTaskAsync()时,它会抛出InnerException = {"This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread."}。这是因为它从另一个线程向我的集合中添加项目。有什么想法吗? - Jepoy_D_Learner
1
从.NET 4.5开始,您可以使用BindingOperations.EnableCollectionSynchronization方法(请查看MSDN或此博客文章http://www.jonathanantoine.com/2011/09/24/wpf-4-5-part-7-accessing-collections-on-non-ui-threads/) - Julien
1
感谢您的回答。由于我在当时并不真正了解自己需要什么,因此我将您的答案作为这个问题的答案。更新一下,我已经发布了这个新问题作为对这篇文章的跟进问题:https://dev59.com/k2Uo5IYBdhLWcg3w2CWJ。 - Jepoy_D_Learner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接