Как Unit Test DelegateCommand, который вызывает асинхронные методы в MVVM

Я новичок в тестировании модулей MVVM и использовании PRISM в моем проекте. Я выполняю модульное тестирование в нашем текущем проекте и не испытываю недостатка в поиске ресурсов в Интернете, которые расскажут мне, как totentCommand вызывает метод async. Это следующий вопрос к моему сообщению - Как Unit Test ViewModel с методом async. о том, как Unit Test использовать асинхронные методы в 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
                }
            }
        }

также есть команда в моей виртуальной машине, которая вызывает метод async выше

  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 это потому, что он не ждет завершения асинхронного метода.

Ответ 1

Я написал класс AsyncCommand, который возвращает объект Task из метода Execute. Затем вам нужно явно реализовать 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();
    }
}

Затем вы можете передать async задачи-возвращающие методы в класс команд:

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

Пользовательский интерфейс, с другой стороны, вызовет явно реализованный метод ICommand.Execute, который позаботится о ожидании задачи.

(*) Тем временем я заметил, что если вы будете следовать общим соглашениям об именах, метод возврата задачи должен фактически называться ExecuteAsync.

Ответ 2

Поскольку я не могу добавлять комментарии, для полноты в PRISM 6, вы можете попробовать:

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

Ответ 3

В Prism 6 вы можете создать DelegateCommand и DelegateCommand<T> из обработчика async.

Например:

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