Exception returned by Should.ThrowAsync<T> has InnerException property equal to null
edumserrano opened this issue · comments
I was trying to do an assert on the inner exception returned by an async method and was using the Should.ThrowAsync<T>
for that.
The test below is a representation of the problem I encountered. The local method ThrowExceptionWithInnerException
is simulating an async
method in my code that throws an exception with an inner exception.
When I run the test below I get a NullReferenceException
because the expectedException.InnerException is null
.
[Fact]
public async Task Demo()
{
async Task ThrowExceptionWithInnerException()
{
await Task.Delay(1);
var innerException = new InvalidOperationException("inner");
var exception = new TaskCanceledException("outer", innerException);
throw exception;
}
var expectedException = await Should.ThrowAsync<TaskCanceledException>(ThrowExceptionWithInnerException());
expectedException.InnerException.ShouldBeOfType<InvalidOperationException>(); // test fails with: "System.NullReferenceException : Object reference not set to an instance of an object." because expectedException.InnerException is null
}
As a workaround for these cases I've been doing:
[Fact]
public async Task DemoWorkaround()
{
async Task ThrowExceptionWithInnerException()
{
await Task.Delay(1);
var innerException = new InvalidOperationException("inner");
var exception = new TaskCanceledException("outer", innerException);
throw exception;
}
try
{
await ThrowExceptionWithInnerException();
}
catch (TaskCanceledException expectedException)
{
expectedException.InnerException.ShouldBeOfType<InvalidOperationException>();
}
}
I searched the docs and existing issues (open and closed) but didn't find anything about this. Is my expectation that the InnerException
property should NOT be null
incorrect?
I'm new to this project, but thought I would have a dig in the code... Ultimately this line is the culprit. Because your outer exception is a TaskCanceledException
the NET method ContinueWith
gives a task where IsFaulted
is false
and isCanceled
is true
. Infact, if we place a debugger in the code we can see there is no original exception, and therefore no inner exception:
If the test has an outer exception of any other type, say like:
[Fact]
public async Task DemoWorks()
{
async Task ThrowExceptionWithInnerException()
{
await Task.Delay(1);
var innerException = new InvalidOperationException("inner");
var exception = new ArgumentException("outer", innerException);
throw exception;
}
var expectedException = await Should.ThrowAsync<ArgumentException>(ThrowExceptionWithInnerException());
expectedException.InnerException.ShouldBeOfType<InvalidOperationException>();
}
Then it works, and we can see with a debugger is hits the earlier lines:
Given it's something fundamental with ContinueWith
I couldn't see an easy fix, but maybe the main project people will comment further :).