In the past, within my team at Innoveo, we had several discussions about the best way to unit test async WPF ICommand. We value quality, so testing is essential to us. We decided to make the methods called by the command internal
so that our tests could call those.
What is the problem with unit testing an Async WPF ICommand? The problem is that the command is an async void
method! So, you have no way to await
the end of the execution of your command. So, your test might assert on things that are still executing.
Some weeks ago, a question about “Extract and Override call” taken from the book “Working Effectively with Legacy Code” for testing was raised on the French DevApps community. I immediately thought of the discussions about testing async WPF ICommand and proposed that solution as a pragmatic solution that is not only easy to implement but also very simple to understand. Some did not like that solution because it was not perfect. I agree, but sometimes it is good to be pragmatic.
The discussion went further about async void
testing and Gérald Barré from Meziantou’s blog, which I highly recommend, answered it interestingly! He made a blog post about his solution “Awaiting an async void method in .NET“ which leverage his own implementation of a SynchronizationContext and TaskCompletionSource
.
I decided to apply his solution to our original question; “How to test an async WPF ICommand”?
I quickly created a small .NET 6 WPF project and added to it the Windows Community Toolkit MVVM nuget. I copy-pasted the code from Gérald to it and wrote the following tests.
1 | [ ] |
The first one fails because nothing awaits the execution of the WPF command. The second one succeeds because the test is awaiting the command execution. Nice, very nice!
And here is the code of the MainWindowViewModel
class.
1 | public class MainWindowViewModel : ObservableObject |
So, that is one way of solving testing an async WPF ICommand. But it is not the only way.
Windows Community Toolkit MVVM provides the AsyncRelayCommand
which implements IAsyncRelayCommand
providing an ExecuteAsync
method returning a Task
which can be awaited.
1 | [ ] |
And in this case we need to declare the command as an IAsyncRelayCommand
1 | private IAsyncRelayCommand? _otherClick; |
and use the ExecuteAsync
method in our test
1 | [ ] |
Conclusion
As a developer, I think it is always good to be pragmatic but this does not avoid to search for better solutions.