How to Unit Test DelegateCommand that calls async

2020-02-04 12:04发布

I am new to Unit Testing MVVM and using PRISM on my project. I am implementing Unit Testing on our current project and not having luck finding resources online that would tell me how totest DelegateCommand that calls async method. This is a follow up question to my post - How to Unit Test a ViewModel with async method. on how to unit test an async methods in MVVM and was answered that public methods can be tested using async TestMethod. This scenario will work only if the method that I want to test are public methods.

The problem is I want to test my DelegateCommand as this are the only public details that I want to expose on other classes and everything else are private. I can expose my private methods as public but I will never do this as its a bad design. I am not sure on how to go about this - Is DelegateCommand needs to be tested, or there are some other work around this? I am interested to know how other go about this and somehow lead me to the right path.

Here are my codes again

 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
                }
            }
        }

also here is the command in my VM that calls the async method above

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

and now my Test Method goes like

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

Currently what I am getting is that my ViewModel.SimpleTask = null this is because it does not wait for the async method to finish.

3条回答
淡お忘
2楼-- · 2020-02-04 12:48

Since I cannot add comments, for completeness sake, in PRISM 6 you could try:

ParsingCommand = new DelegateCommand<string>(async (x) => await StartParsing(x));
查看更多
倾城 Initia
3楼-- · 2020-02-04 12:53

I wrote an AsyncCommand class that returns a Task object from the Execute method. You then need to implement ICommand.Execute explicitly, awaiting the Task from your Execute implementation:

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();
    }
}

You can then pass async Task-returning methods to the command class:

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;
    }
}

In your unit tests, you simply await the Execute method:

[TestMethod]
public async Task TestAsyncCommand()
{
    var viewModel = new ViewModel();

    Assert.IsFalse(viewModel.Executed);
    await viewModel.AsyncCommand.Execute();

    Assert.IsTrue(viewModel.Executed);
}

The UI, on the other hand, will call the explicitly implemented ICommand.Execute method which takes care of awaiting the task.

(*) In the meantime I noticed that if you follow common naming conventions, the Task-returning method should actually be named ExecuteAsync.

查看更多
贼婆χ
4楼-- · 2020-02-04 12:53

In Prism 6, you can create DelegateCommand and DelegateCommand<T> from async handler.

For example:

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

查看更多
登录 后发表回答