Consider the following highly simplified viewmodel for fetching and showing a list of projects:
public class ProjectListViewModel
{
private readonly IWebService _webService;
public ICommand RefreshCommand { get; }
// INotifyPropertyChanged implementation skipped for brevity
public ObservableCollection<Project> Projects { get; set; }
public ProjectListViewModel(IWebService serverApi)
{
_serverApi = serverApi;
// ICommand implemented by Xamarin.Forms
RefreshCommand = new Command(async () => await RefreshAsync());
}
private async Task RefreshAsync()
{
try
{
Projects = await _webService.GetProjectsAsync();
}
catch (TaskCanceledException)
{
// Empty (task only cancelled when we are navigating away from page)
}
}
}
Using NUnit and Moq, I'm trying test that when GetProjectsAsync
throws a TaskCanceledException
, the ViewModel will catch it. The closest I get is this:
[Test]
public void When_Refreshing_Catches_TaskCanceledException()
{
// Arrange
webService = new Mock<IServerApi>();
webService.Setup(mock => mock.GetProjectsAsync())
.ThrowsAsync(new TaskCanceledException());
vm = new ProjectListViewModel(webService.Object);
// Act and assert
Assert.That(() => vm.RefreshCommand.Execute(null), Throws.Nothing);
}
The test passes, but unfortunately it's faulty - it still passes if I throw e.g. Exception instead of TaskCanceledException. As far as I know, the reason is that the exception doesn't bubble up past the command lambda, async () => await RefreshAsync()
, so no exception thrown by GetProjectsAsync will ever be detected by the test. (When running the actual app however, the TaskCanceledException will bubble up and crash the app if not caught. I suspect this is related to synchronization contexts, of which I have very limited understanding.)
It works if I debug the test - if I mock it to throw Exception, it will break on the line with the command/lambda definition, and if I throw TaskCanceledException, the test will pass.
Note that the results are the same if I use Throws instead of ThrowsAsync. And in case it's relevant, I'm using the test runner in ReSharper 2016.2.
Using nUnit, is it possible at all to unit test exceptions thrown when executing "async" commands like this? Is it possible without writing a custom Command implementation?