How to Unit Test DelegateCommand that calls async methods in MVVM - c#

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.

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.

In Prism 6, you can create DelegateCommand and DelegateCommand<T> from async handler.
For example:
startParsingCommand=DelegateCommand
.FromAsyncHandler(StartParsingAsync,CanStartParsing)
.ObservesProperty(()=> IsParserStarted);

Since I cannot add comments, for completeness sake, in PRISM 6 you could try:
ParsingCommand = new DelegateCommand<string>(async (x) => await StartParsing(x));

Related

How to write xUnit Test for a method which calls another method in its body?

This is the class contains EnqueueJobAsync method which I want to write test for it :
public class ConsumerBaseForTesting
{
protected IJobStore JobStore { get; private set; }
public ConsumerBaseForTesting(IJobStore jobStore)
{
JobStore = jobStore;
}
public async Task<IJob> EnqueueJobAsync(IJob job)
=> await JobStore.CreateAsync(job);
}
This is my test which Fails and its actual return is always NULL !
public class ConsumerBaseTest
{
private readonly Mock<IJobStore> _moqIJobStore;
private readonly ConsumerBaseForTesting _consumerBase;
public ConsumerBaseTest()
{
_moqIJobStore = new Mock<IJobStore>();
_consumerBase = new ConsumerBaseForTesting(_moqIJobStore.Object);
}
[Theory]
[ClassData(typeof(JobClassForTesting))]
public async Task EnqueueJobAsyncTest(IJob job)
{
var jobResult = await _consumerBase.EnqueueJobAsync(job);
Assert.Equal(job, jobResult);
}
}
The mock needs to be setup to do two things in order to replicate the expected behavior.
It needs to return the passed job in a completed task.
//...
public ConsumerBaseTest() {
_moqIJobStore = new Mock<IJobStore>();
_consumerBase = new ConsumerBaseForTesting(_moqIJobStore.Object);
//setup the mock to capture and return the job when CreateAsync(IJob job) is invoked
_moqIJobStore
.Setup(_ => _.CreateAsync(It.IsAny<IJob>()))
.Returns((IJob x) => Task.FromResult(x)); }
//...
.Returns((IJob x) => Task.FromResult(x)) captures the argument and returns completed Task<IJob>

Populate ObservableCollection with data from awaited Task.Run

I am making Xamarin application and I have a problem with display data in my list view. So, basically I have web service from where I fetch data (that endpoint is non-async), so I don't want to block UI thread of my application, so I wrap the call to web service in Task.Run and await that task.
public class HomeDetailPageViewModel : ViewModelNavigatable
{
public ObservableCollection<CarViewModel> cars;
public ObservableCollection<CarViewModel> Cars
{
get { return this.cars; }
}
public HomeDetailPageViewModel(INavigationService navigationService)
:base(navigationService)
{
this.cars = new ObservableCollection<CarViewModel>();
AppearingCommand = new AsyncCommand(this.OnAppearingCommandExecuted);
}
public IAsyncCommand AppearingCommand { get; set; }
public async Task OnAppearingCommandExecuted()
{
using (UserDialogs.Instance.Loading("Loading..."))
{
this.Cars.Clear();
IEnumerable<Car> carsFromEndpoint = await Task.Run(() => CarEndpoint.GetAll(client.Context)).ConfigureAwait(false);
Device.BeginInvokeOnMainThread(() =>
{
foreach (var carFromEndpoint in carsFromEndpoint.ToList())
this.Cars.Add(new CarViewModel
{
Manufacturer = carFromEndpoint.Code,
Model = carFromEndpoint.Name,
Price = carFromEndpoint.Price,
Year = carFromEndpoint.Year
});
});
}
}
}
As I said CarEndpoint.GetAll(client.Context) is synchronous endpoint. If I use:
Task.Run(() => CarEndpoint.GetAll(client.Context)).Result or
CarEndpoint.GetAll(client.Context)
everything works as expected but that is not acceptable because it's block UI thread until Task is finished. I know it is not a good idea to use Task.Run to make fake asynchronous calls, but I don't see another way to keep app responsive, because I cannot change the endpoint of web service.
Thank you for the answer.
Cheers :)
I know it is not a good idea to use Task.Run to make fake asynchronous calls
Using Task.Run to unblock a UI thread - even in a fake-asynchronous way - is fine.
I cannot change the endpoint of web service.
This sentence doesn't make as much sense. All web services are asynchronous by nature. However, it's possible that your client-side library is strictly synchronous, in which case Task.Run is a fine way to unblock the UI while calling it. But you'll be working around a limitation in the client-side library, not the web service itself.
I have a problem with display data in my list view.
Your problem is likely due to the IEnumerable<T>, not Task.Run. Here's some code I expect would work; the key is to move the ToList inside the Task.Run delegate:
public async Task OnAppearingCommandExecuted()
{
using (UserDialogs.Instance.Loading("Loading..."))
{
this.Cars.Clear();
List<Car> carsFromEndpoint = await Task.Run(() => CarEndpoint.GetAll(client.Context).ToList());
foreach (var carFromEndpoint in carsFromEndpoint)
this.Cars.Add(new CarViewModel
{
Manufacturer = carFromEndpoint.Code,
Model = carFromEndpoint.Name,
Price = carFromEndpoint.Price,
Year = carFromEndpoint.Year
});
}
}
Notes:
BeginInvokeOnMainThread is unnecessary if you remove the ConfigureAwait(false); the await resumes on the UI thread for us. In fact, BeginInvokeOnMainThread is a code smell.
You probably don't want an asynchronous command here; you just want to asynchronously load data.
When you're using Async commands in MVVM, the main problem is NotSupportedException while modifying the ObservableCollection. All the rest don't cause any issues if you're careful with concurrency.
Here's a class representing ObservableCollection for use from multiple threads which forwards all actions to SynchronizationsContext of the Thread where it was constructed.
Just use it instead of ObservableCollection (not mine, grabbed from GitHub)
public class AsyncObservableCollection<T> : ObservableCollection<T>
{
private readonly SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
public AsyncObservableCollection() : base() { }
public AsyncObservableCollection(IEnumerable<T> collection) : base(collection) { }
public AsyncObservableCollection(List<T> list) : base(list) { }
private void ExecuteOnSyncContext(Action action)
{
if (SynchronizationContext.Current == _synchronizationContext)
action();
else
_synchronizationContext.Send(_ => action(), null);
}
protected override void InsertItem(int index, T item) => ExecuteOnSyncContext(() => base.InsertItem(index, item));
protected override void RemoveItem(int index) => ExecuteOnSyncContext(() => base.RemoveItem(index));
protected override void SetItem(int index, T item) => ExecuteOnSyncContext(() => base.SetItem(index, item));
protected override void MoveItem(int oldIndex, int newIndex) => ExecuteOnSyncContext(() => base.MoveItem(oldIndex, newIndex));
protected override void ClearItems() => ExecuteOnSyncContext(() => base.ClearItems());
}
And this AsyncRelayCommand class that I've made with help on StackOverflow (Russian Community). It doesn't freeze anything.
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object param);
}
public class AsyncRelayCommand : IAsyncCommand
{
private bool _isExecuting;
private readonly SynchronizationContext _context;
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public AsyncRelayCommand(Action<object> execute, Predicate<object> canExecute = null)
=> (_execute, _canExecute, _context) = (execute, canExecute, SynchronizationContext.Current);
private void InvalidateRequerySuggested()
{
if (_context.Equals(SynchronizationContext.Current))
CommandManager.InvalidateRequerySuggested();
else
_context.Send(_ => CommandManager.InvalidateRequerySuggested(), null);
}
public bool CanExecute(object parameter) => !_isExecuting && (_canExecute == null || _canExecute(parameter));
public async Task ExecuteAsync(object parameter)
{
if (CanExecute(parameter))
{
try
{
_isExecuting = true;
InvalidateRequerySuggested();
await Task.Run(() => _execute(parameter));
}
finally
{
_isExecuting = false;
InvalidateRequerySuggested();
}
}
}
public void Execute(object parameter) => _ = ExecuteAsync(parameter);
}
Usage as with regular RelayCommand class from this article.
private IAsyncCommand _myAsyncCommand;
// "lazy" instantiation with single instance
public IAsyncCommand MyAsyncCommand => _myAsyncCommand ?? (_myAsyncCommand = new AsyncRelayCommand(parameter =>
{
}));
<Button Content="Click me!" Command="{Binding MyAsyncCommand}" />
CommandParameter is also supported.
With this you don't need pushing collecton change calls into UI Thread, use it as in usual sync code. And Thread.Sleep() or heavy job in the command will not freeze UI because it will run on a separate thread.
Usage with your code
private IAsyncCommand _appearingCommand;
public AsyncObservableCollection<CarViewModel> cars; // are you sure that it must be public?
public HomeDetailPageViewModel(INavigationService navigationService)
:base(navigationService)
{
this.cars = new AsyncObservableCollection<CarViewModel>();
}
public AsyncObservableCollection<CarViewModel> Cars
{
get => this.cars;
}
public IAsyncCommand AppearingCommand => _appearingCommand ?? (_appearingCommand = new AsyncRelayCommand(parameter =>
{
// edit: fixed regarding to the accepted answer
List<Car> carsFromEndpoint = CarEndpoint.GetAll(client.Context).ToList();
foreach (var carFromEndpoint in carsFromEndpoint)
this.Cars.Add(new CarViewModel
{
Manufacturer = carFromEndpoint.Code,
Model = carFromEndpoint.Name,
Price = carFromEndpoint.Price,
Year = carFromEndpoint.Year
});
}));

(A)wait for class to finish instantiate

I try to wait for the class to be finished with instantiate.
My architecture is the following. Cook is inheriade from CookChief.
And if I instantiate cook, CookChief is creating himself, but CookChief is calling 1 other class named Cookhelper the cookhelper is waiting for a input and for this input method i want to wait in Cook.
The thing is iam creating this in MVVM Galasoft and my entry point is the CookViewmodel, with a relaycommand.
In the code below you can see my architecture. To say it short I want to wait until this bool processed = await Task.Run(() => ValidateForDeviceId()); is finished.
My first step was to outsource the constructer of each class. And create a init method.
This is my code:
public CookViewModel()
{
startCookButtonCommand = new RelayCommand(Cook);
}
private async Task Cook()
{
cook.Init();
}
public class Cook : CookChief
{
public Cook()
{
}
public async Task Init()
{
await this.CookChiefInit();
//here I want to wait until CookChiefInit is finished
Cooking();
}
public void Cooking()
{
MessageBox.Show("Input received");
}
}
Now the Cookchief:
public Cookchief()
{
}
protected async Task CookchiefInit()
{
this.Cookhelper = new Cookhelper();
Cookhelper.CookHelperInit();
}
And in the CookHelper we do this:
public CookHelper()
{
}
public void CookHelperInit()
{
this.driverWindow = new DriverWindow();
startProc();
}
private async void startProc()
{
ShowOrCloseDriverWindow(true);
//this is the task what we wait for before we can repeat
bool processed = await Task.Run(() => ValidateForDeviceId());
if(processed)
{
ShowOrCloseDriverWindow(false);
}
else
{
MessageBox.Show("DriverError");
}
}
private bool ValidateForDeviceId()
{
for (; ; )
{
this.deviceId = Input.deviceId;
if (deviceId > 0)
{
break;
}
}
return true;
}
Per the discussion in the comments, the problem here was that the initialization routine mixed synchronous and asynchronous methods and calls. Additionally, some async methods were called without the await keyword. The solution was to make all calls asynchronous and await them.
cook.Init() needs an await:
private async Task Cook()
{
await cook.Init();
}
In CookchiefInit(), the CookHelperInit() call needs to be awaited:
protected async Task CookchiefInit()
{
this.Cookhelper = new Cookhelper();
Cookhelper.CookHelperInit();
}
In order to await CookHelperInit(), it needs to be made asynchronous. The startProc() call is to an async method, so it must also be awaited:
public async Task CookHelperInit()
{
this.driverWindow = new DriverWindow();
await startProc();
}

Automated test for a async Command in MVVM

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 IAsyncCommands 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.Sleepas 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);

How to unit test RelayCommand that Executes an async method?

As there is no RelayCommandAsync (at least not that I know of), how to test this scenario. For example:
public RelayCommand LoadJobCommand
{
get
{
return this.loadJobCommand ?? (
this.loadJobCommand =
new RelayCommand(
this.ExecuteLoadJobCommandAsync));
}
}
private async void ExecuteLoadJobCommandAsync()
{
await GetData(...);
}
Test:
vm.LoadJobCommand.Execute()
Assert.IsTrue(vm.Jobs.Count > 0)
It really depends on what you are trying to test:
Test that the RelayCommand is properly hooked up and calls your async
method?
or
Test that the Async Method logic is correct?
1. Testing the RelayCommand trigger
1.a Using External Dependencies to verify
From my personal experience the easiest way to test that the trigger is wired up correctly to execute the command and then test that your class has interacted with another external class somewhere as expected. E.g.
private async void ExecuteLoadJobCommandAsync()
{
await GetData(...);
}
private async void GetData(...)
{
var data = await _repo.GetData();
Jobs.Add(data);
}
Its fairly easy to test that your repo gets called.
public void TestUsingExternalDependency()
{
_repo.Setup(r => r.GetData())
.Returns(Task.Run(() => 5))
.Verifiable();
_vm.LoadJobCommand.Execute(null);
_repo.VerifyAll();
}
I sometimes even do this, so that it doesn't try to process everything:
[Test]
public void TestUsingExternalDependency()
{
_repo.Setup(r => r.GetData())
.Returns(() => { throw new Exception("TEST"); })
.Verifiable();
try
{
_vm.LoadJobCommand.Execute(null);
}
catch (Exception e)
{
e.Message.Should().Be("TEST");
}
_repo.VerifyAll();
}
1.b Using a Scheduler
Another option is to use a scheduler, and schedule tasks using that.
public interface IScheduler
{
void Execute(Action action);
}
// Injected when not under test
public class ThreadPoolScheduler : IScheduler
{
public void Execute(Action action)
{
Task.Run(action);
}
}
// Used for testing
public class ImmediateScheduler : IScheduler
{
public void Execute(Action action)
{
action();
}
}
Then in your ViewModel
public ViewModelUnderTest(IRepository repo, IScheduler scheduler)
{
_repo = repo;
_scheduler = scheduler;
LoadJobCommand = new RelayCommand(ExecuteLoadJobCommandAsync);
}
private void ExecuteLoadJobCommandAsync()
{
_scheduler.Execute(GetData);
}
private void GetData()
{
var a = _repo.GetData().Result;
Jobs.Add(a);
}
And your test
[Test]
public void TestUsingScheduler()
{
_repo.Setup(r => r.GetData()).Returns(Task.Run(() => 2));
_vm = new ViewModelUnderTest(_repo.Object, new ImmediateScheduler());
_vm.LoadJobCommand.Execute(null);
_vm.Jobs.Should().NotBeEmpty();
}
2. Testing the GetData Logic
If you are looking to test get GetData() logic or even the ExecuteLoadJobCommandAsync() logic. Then you should definitely make the method you want to test, as Internal, and mark your assmebly as InternalsVisibleTo so that you can call those methods directly from your test class.
Why don't you cover GetData(...) method with tests? I don't see any sense in testing relay commands
I was not using async/await but I have run in to a similar problem in the past. The situation I was in is the method called a Task.Run( inside of itself and the unit test was verifying that the ViewModel was calling the service with the correct number of times with the correct parameters.
The way we solved this was we had our Mock of the service that was being called use a ManualResetEventSlim, then the unit test waited for that reset event to be called before proceeding.
[TestMethod]
public void EXAMPLE()
{
using (var container = new UnityAutoMoqContainer())
{
//(SNIP)
var serviceMock = container.GetMock<ITreatmentPlanService>();
var resetEvent = new ManualResetEventSlim();
serviceMock.Setup(x=>x.GetSinglePatientViewTable(dateWindow, currentPatient, false))
.Returns(() =>
{
resetEvent.Set();
return new ObservableCollection<SinglePatientViewDataRow>();
});
var viewModel = container.Resolve<SinglePatientViewModel>();
//(SNIP)
viewModel.PatientsHadTPClosed(guids, Guid.NewGuid());
waited = resetEvent.Wait(timeout);
if(!waited)
Assert.Fail("GetSinglePatientViewTable was not called within the timeout of {0} ms", timeout);
//(SNIP)
serviceMock.Verify(x => x.GetSinglePatientViewTable(dateWindow, currentPatient, false), Times.Once);
}
}
If this approach works or not for you all depends on what your unit test is actually testing. Because you check Assert.IsTrue(vm.Jobs.Count > 0) it looks like you have extra logic that is being done after the await GetData(...); call, so this might not be applicable for your current problem. However, this may be helpful for other unit tests you need to write for your view model.

Categories