Reputation: 197
It is my first time when I write unit tests for async method. I am using xUnit. I searched SO with no promissing results. Best I found, but didnt work for me, is to implement IAsyncLifetime
from THIS example. I will be thankful for any hints how to trouble shoot this problem.
Currently what I have. In tested VM I have a command:
public ICommand TestResultsCommand { get; private set; }
And the command is initialized in the VM constructor as below:
TestResultsCommand = new DelegateCommand(OnTestResultExecuteAsync);
Command calls method:
private async void OnTestResultExecuteAsync(object obj)
{
TokenSource = new CancellationTokenSource();
CancellationToken = TokenSource.Token;
await TestHistoricalResultsAsync();
}
TestHistoricalResultsAsync method's signature is as below:
private async Task TestHistoricalResultsAsync()
Now lets go to the unit test project. Currently in test class I have a method:
[Fact]//testing async void
public void OnTestResultExecuteAsync_ShouldCreateCancellationTokenSource_True()
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = tokenSource.Token;
_viewModel.TestResultsCommand.Execute(null);
Assert.Equal(cancellationToken.CanBeCanceled, _viewModel.CancellationToken.CanBeCanceled);
Assert.Equal(cancellationToken.IsCancellationRequested, _viewModel.CancellationToken.IsCancellationRequested);
}
And the test drops me an exception:
Message: System.NullReferenceException : Object reference not set to an instance of an object.
The stack trace for the exception is:
Thank you in advance for your time and suggestions.
Upvotes: 0
Views: 859
Reputation: 457302
One of the problems of async void
methods is that they're difficult to test. For your problem, most developers do one of these:
IAsyncCommand
interface.async Task
and public.See my MSDN magazine article on the subject for details.
Here's an example with the second approach:
TestResultsCommand = new DelegateCommand(async () => await OnTestResultExecuteAsync());
public async Task OnTestResultExecuteAsync()
{
TokenSource = new CancellationTokenSource();
CancellationToken = TokenSource.Token;
await TestHistoricalResultsAsync();
}
[Fact]
public async Task OnTestResultExecuteAsync_ShouldCreateCancellationTokenSource_True()
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = tokenSource.Token;
await _viewModel.OnTestResultExecuteAsync();
Assert.Equal(cancellationToken.CanBeCanceled, _viewModel.CancellationToken.CanBeCanceled);
Assert.Equal(cancellationToken.IsCancellationRequested, _viewModel.CancellationToken.IsCancellationRequested);
}
If you don't want to expose async Task
methods just for the sake of unit testing, then you can use an IAsyncCommand
of some kind; either your own (as detailed in my article) or one from a library (e.g., MvvmCross). Here's an example using the MvvmCross types:
public IMvxAsyncCommand TestResultsCommand { get; private set; }
TestResultsCommand = new MvxAsyncCommand(OnTestResultExecuteAsync);
private async Task OnTestResultExecuteAsync() // back to private
{
TokenSource = new CancellationTokenSource();
CancellationToken = TokenSource.Token;
await TestHistoricalResultsAsync();
}
[Fact]
public async Task OnTestResultExecuteAsync_ShouldCreateCancellationTokenSource_True()
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = tokenSource.Token;
await _viewModel.TestResultsCommand.ExecuteAsync();
Assert.Equal(cancellationToken.CanBeCanceled, _viewModel.CancellationToken.CanBeCanceled);
Assert.Equal(cancellationToken.IsCancellationRequested, _viewModel.CancellationToken.IsCancellationRequested);
}
If you prefer the IMvxAsyncCommand
approach but don't want the MvvmCross dependency, it's not hard to define your own IAsyncCommand
and AsyncCommand
types.
Upvotes: 2