21

The following asynchronous xUnit.net test with a lambda marked with the async modifier fails by reporting that no exception was thrown:

    [Theory, AutoWebData]
    public async Task SearchWithNullQueryThrows(
        SearchService sut,
        CancellationToken dummyToken)
    {
        // Fixture setup
        // Exercise system and verify outcome
        Assert.Throws<ArgumentNullException>(async () =>
            await sut.SearchAsync(null, dummyToken));
        // Teardown
    }

To make sure that an ArgumentNullException is actually thrown I explicitly used a try-catch block. It worked, however the resulting code is not clean (compared to the first test):

[Theory, AutoWebData]
public async Task SearchWithNullQueryThrows(
    SearchService sut,
    CancellationToken dummyToken)
{
    // Fixture setup
    var expected = typeof(ArgumentNullException);
    Type actual = null;
    // Exercise system
    try
    {
        await sut.SearchAsync(null, dummyToken);
    }
    catch (ArgumentNullException e)
    {
        actual = e.GetType();
    }
    // Verify outcome
    Assert.Equal(expected, actual);
    // Teardown
}

Why the Assert.Throws<T> with the lambda marked with the async modifier fails?

svick
  • 236,525
  • 50
  • 385
  • 514
Nikos Baxevanis
  • 10,868
  • 2
  • 46
  • 80
  • 3
    This is a known issue: http://xunit.codeplex.com/workitem/9799 – DaveShaw Jan 01 '13 at 00:50
  • +1 It seems to be fixed in snapshot `03e3be9a6781` which is before the snapshot `16883cb2351f` where release `2.0.0-alpha` has been associated. However it still doesn't work after updating the solution with NuGet Package Manager. – Nikos Baxevanis Jan 01 '13 at 20:53

2 Answers2

39

Update

This has been solved in xUnit 2, with the addition of Assert.ThrowsAsync.


I am suspecting that Assert.Throws is not async-aware. I recommend raising this issue with the xUnit team, suggesting a ThrowsAsync be added.

An async delegate in this case is returning Task or Task<T>, and the ArgumentNullException is not thrown out of the delegate directly; instead, it is placed on the Task (Task.Exception.InnerException). Assert.Throws is expecting the exception to be thrown out of the delegate directly, not placed on a property of the return value.

You can create your own AssertEx.ThrowsAsync as such:

public static async Task ThrowsAsync<TException>(Func<Task> func)
{
  var expected = typeof(TException);
  Type actual = null;
  try
  {
    await func();
  }
  catch (Exception e)
  {
    actual = e.GetType();
  }
  Assert.Equal(expected, actual);
}

which can be used as such:

[Theory, AutoWebData]
public async Task SearchWithNullQueryThrows(
    SearchService sut,
    CancellationToken dummyToken)
{
    // Fixture setup
    // Exercise system and verify outcome
    await AssertEx.ThrowsAsync<ArgumentNullException>(async () =>
        await sut.SearchAsync(null, dummyToken));
    // Teardown
}

I use a similar approach in MSTest.

dcastro
  • 66,540
  • 21
  • 145
  • 155
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • I modified your code slightly, as I believe as it stood only 'ArgumentNullException' would ever get as far as the `Assert`. Another thing that might be worth considering is that the original `Assert.Throws` *returns* the exception (so that you can test exception message too, for example). – Benjol Feb 25 '13 at 08:57
3

If you also need to return the exception to verify it then this might be useful:

public static async Task<Exception> AssertThrowsAsync<TException>(Func<Task> func)
    {
        var expected = typeof (TException);
        Exception exception = null;
        Type actual = null;
        try
        {
            await func();
        }
        catch (Exception e)
        {
            actual = e.GetType();
            exception = e;
        }
        Assert.NotNull(exception);
        Assert.Equal(expected, actual);
        return exception;
    }
wortho
  • 113
  • 1
  • 8