Как выполнить модульный тест DelegateCommand, который вызывает асинхронные методы в MVVM

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

3 ответа

Решение

Я написал класс 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();
    }
}

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

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,

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

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

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

Например:

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

Другие вопросы по тегам