I've been Googling and even Bing-ing and I haven't come up with anything that is satisfying.
I have a ViewModel which has some commands, such as: SaveCommand
, NewCommand
and DeleteCommand
. My SaveCommand
executes a save to a file operation, which I want to be an async
operation so that the UI doesn't wait for it.
My SaveCommand
is an instance of AsyncCommand, which implements ICommand
.
SaveCommand = new AsyncCommand(
async param =>
{
Connection con = await Connection.GetInstanceAsync(m_configurationPath);
con.Shoppe.Configurations = new List<CouchDbConfig>(m_configurations);
await con.SaveConfigurationAsync(m_configurationPath);
//now that its saved, we reload the Data.
await LoadDataAsync(m_configurationPath);
},
...etc
Now I'm building a test for my ViewModel. In it, I create a new thing with the NewCommand
, I modify it and then use the SaveCommand
.
vm.SaveCommand.Execute(null);
Assert.IsFalse(vm.SaveCommand.CanExecute(null));
My CanExecute
method (not shown) of the SaveCommand
should return False
just after the item has been saved (there's no point saving an unchanged item). However, the Assert shown above fails all the time because I am not waiting for the SaveCommand
to finish executing.
Now, I can't wait for it to finish executing because I can't. The ICommand.Execute
doesn't return a Task
. And if I change the AsyncCommand
to have its Execute
return a Task
then it won't implement the ICommand
interface properly.
So, the only thing I think I can do now, for testing purposes, is for the AsynCommand
to have a new function:
public async Task ExecuteAsync(object param) { ... }
And thus, my test will run (and await
) the ExecuteAsync
function and the XAML UI will run the ICommand.Execute
method in which it does not await
.
I don't feel happy about doing my proposed solution method as I think, and hope, and wish that there is a better way.
Is what I suggest, reasonable? Is there a better way?
Doing while (vm.SaveCommand.Executing) ;
seems like busy waiting and I prefer to avoid that.
The other solution using AsyncCommand
from Stephen Cleary seems a bit overkill for such a simple task.
My proposed way doesn't break encapsulation - the Save
method doesn't expose any internals. It just offers another way of accessing the same functionality.
My solution seems to cover everything that's needed in a simple and straightforward way.
I would suggest refactoring this code:
SaveCommand = new AsyncCommand(
async param =>
{
Connection con = await Connection.GetInstanceAsync(m_configurationPath);
con.Shoppe.Configurations = new List<CouchDbConfig>(m_configurations);
await con.SaveConfigurationAsync(m_configurationPath);
//now that its saved, we reload the Data.
await LoadDataAsync(m_configurationPath);
});
to:
SaveCommand = new RelayCommand(async param => await Save(param));
public async Task Save(object param)
{
Connection con = await Connection.GetInstanceAsync(m_configurationPath);
con.Shoppe.Configurations = new List<CouchDbConfig>(m_configurations);
await con.SaveConfigurationAsync(m_configurationPath);
//now that its saved, we reload the Data.
await LoadDataAsync(m_configurationPath);
}
Just a note: I changed AsyncCommand
to RelayCommand
which can be found in any MVVM framework. It just receives an action as parameter and runs that when the ICommand.Execute
method is called.
I made an example using the NUnit framework which has support for async
tests:
[Test]
public async Task MyViewModelWithAsyncCommandsTest()
{
// Arrange
// do view model initialization here
// Act
await vm.Save(param);
// Assert
// verify that what what you expected actually happened
}
and in the view bind the command like you would do normally:
Command="{Binding SaveCommand}"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With