shouldly / shouldly

Should testing for .NET—the way assertions should be!

Home Page:https://docs.shouldly.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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:

image

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:

image


Given it's something fundamental with ContinueWith I couldn't see an easy fix, but maybe the main project people will comment further :).