I have an asynchronous Command
class, like so:
public AsyncDelegateCommand(Func<Task> execute, Func<bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public virtual bool CanExecute(object parameter)
{
if(executing)
return false;
if(canExecute == null)
return true;
return canExecute();
}
public async void Execute(object parameter) // Notice "async void"
{
executing = true;
CommandManager.InvalidateRequerySuggested();
if(parameter != null && executeWithParameter != null)
await executeWithParameter(parameter);
else if(execute != null)
await execute();
executing = false;
CommandManager.InvalidateRequerySuggested();
}
And it is called like so:
FindProductCommand = new AsyncDelegateCommand(TryToFindProduct, () => CanFindProduct() && connector.HasConnection);
private async Task TryToFindProduct()
{
//code
}
When I unit tests, it works just fine, since I'm returning instantly from tasks.
However, when writing my integration test, I run into trouble. I am not able to await Execute
, since it is void
, and I am not able to change it to Task
. I end up doing this: :/
findProductViewModel.FindProductCommand.Execute(null);
Thread.Sleep(2000);
var informationViewModel = findProductViewModel.ProductViewModel.ProductInformationViewModel;
Assert.AreEqual("AFG00", informationViewModel.ProductGroup);
Is there a better solution for this test? Maybe something that is reliant on how long it actually takes, and doesn't estimate how long to wait.
You can refer to a good blog post by @StephenCleary: https://msdn.microsoft.com/en-us/magazine/dn630647.aspx
async void
is generally to be avoided, so he introduces a new interface (and its base implementation) for an async command: IAsyncCommand
. This interface contains a method async Task ExecuteAsync(object parameter)
that you could await in your tests.
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object parameter);
}
public abstract class AsyncCommandBase : IAsyncCommand
{
public abstract bool CanExecute(object parameter);
public abstract Task ExecuteAsync(object parameter);
public async void Execute(object parameter)
{
await ExecuteAsync(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
protected void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
The simplest implementation of such an async command would look like this:
public class AsyncCommand : AsyncCommandBase
{
private readonly Func<Task> _command;
public AsyncCommand(Func<Task> command)
{
_command = command;
}
public override bool CanExecute(object parameter)
{
return true;
}
public override Task ExecuteAsync(object parameter)
{
return _command();
}
}
But there are more advanced variants that you can find in the linked blog post. You can use the IAsyncCommand
s in your code all the way, so you can test them. The MVVM framework you use will also be happy, because that interface is based on the ICommand
.
Is there a better solution for this test? Maybe something that is reliant on how long it actually takes, and doesn't estimate how long to wait.
Certainly: Make the command awaitable and await
it. async
void
methods are bad practice and are only meant to be used for event handlers.
There are awaitable commands available in Mvvm.Async and ReactiveUI that you can use or at least take a look at for reference.
If you have a state to check for, you should probably just use SpinWait.SpinUntil:
It will be much more reliable than Thread.Sleep
as it allows you to check if a condition is true before continuing.
e.g.
findProductViewModel.FindProductCommand.Execute(null);
SpinWait.SpinUntil(() => findProductViewModel.ProductViewModel.ProductInformationViewModel != null, 5000);
var informationViewModel = findProductViewModel.ProductViewModel.ProductInformationViewModel;
Assert.AreEqual("AFG00", informationViewModel.ProductGroup);