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.

MainWindowViewModelTests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[TestFixture]
public class MainWindowViewModelTests
{
[Test]
public void Click_FailingToAwaitCommandExecution()
{
var mainWindowViewModel = new MainWindowViewModel();

mainWindowViewModel.Click.Execute(null);

Assert.That(mainWindowViewModel.Upper, Is.EqualTo("BEFORE"));
}

[Test]
public async Task Click_ExpectUpperToBeUpperCase()
{
var mainWindowViewModel = new MainWindowViewModel();

await AsyncVoidSynchronizationContext.Run(
() => mainWindowViewModel.Click.Execute(null));

Assert.That(mainWindowViewModel.Upper, Is.EqualTo("BEFORE"));
}
}

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.

MainWindowViewModel.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MainWindowViewModel : ObservableObject
{
private ICommand? _click;
private string _upper = "before";

public ICommand Click => _click ??= new AsyncRelayCommand(Execute);

public string Upper
{
get => _upper;
set => SetProperty(ref _upper, value);
}

private async Task Execute()
{
await Task.Delay(2000);

Upper = Upper.ToUpper();
}
}

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
2
3
4
5
6
7
8
9
[Test]
public async Task OtherClick_ExpectUpperToBeUpperCase()
{
var mainWindowViewModel = new MainWindowViewModel();

await mainWindowViewModel.OtherClick.ExecuteAsync(null);

Assert.That(mainWindowViewModel.Upper, Is.EqualTo("BEFORE"));
}

And in this case we need to declare the command as an IAsyncRelayCommand

1
2
3
private IAsyncRelayCommand? _otherClick;

public IAsyncRelayCommand OtherClick => _otherClick ??= new AsyncRelayCommand(Execute);

and use the ExecuteAsync method in our test

1
2
3
4
5
6
7
8
9
[Test]
public async Task OtherClick_ExpectUpperToBeUpperCase()
{
var mainWindowViewModel = new MainWindowViewModel();

await mainWindowViewModel.OtherClick.ExecuteAsync(null);

Assert.That(mainWindowViewModel.Upper, Is.EqualTo("BEFORE"));
}

Conclusion

As a developer, I think it is always good to be pragmatic but this does not avoid to search for better solutions.