21

Does anyone have an example of how to unit test an async method in a Windows 8 Metro application, to ensure that it throws the required exception?

Given a class with an async method

public static class AsyncMathsStatic
{
    private const int DELAY = 500;

    public static async Task<int> Divide(int A, int B)
    {
        await Task.Delay(DELAY);
        if (B == 0)
            throw new DivideByZeroException();
        else
            return A / B;
    }
}

I want to write a test method using the new Async.ExpectsException construction. I've tried :-

[TestMethod]
public void DivideTest1()
{
    Assert.ThrowsException<DivideByZeroException>(async () => { int Result = await AsyncMathsStatic.Divide(4, 0); });
}

but of course the test doesn't wait for the async method to complete, and so results in a failed test that the exception hasn't been thrown.

Liam
  • 27,717
  • 28
  • 128
  • 190
Peregrine
  • 4,287
  • 3
  • 17
  • 34

7 Answers7

22

You can use an async Task unit test with the regular ExpectedExceptionAttribute:

[TestMethod]
[ExpectedException(typeof(DivideByZeroException))]
public async Task DivideTest1()
{
  int Result = await AsyncMathsStatic.Divide(4, 0);
}

Update from comment: ExpectedExceptionAttribute on Win8 unit test projects has been replaced with Assert.ThrowsException, which is nicely undocumented AFAICT. This is a good change design-wise, but I don't know why it's only supported on Win8.

Well, assuming that there's no async-compatible Assert.ThrowsException (I can't tell if there is one or not due to lack of documentation), you could build one yourself:

public static class AssertEx
{
  public async Task ThrowsExceptionAsync<TException>(Func<Task> code)
  {
    try
    {
      await code();
    }
    catch (Exception ex)
    {
      if (ex.GetType() == typeof(TException))
        return;
      throw new AssertFailedException("Incorrect type; expected ... got ...", ex);
    }

    throw new AssertFailedException("Did not see expected exception ...");
  }
}

and then use it as such:

[TestMethod]
public async Task DivideTest1()
{
  await AssertEx.ThrowsException<DivideByZeroException>(async () => { 
      int Result = await AsyncMathsStatic.Divide(4, 0);
  });
}

Note that my example here is just doing an exact check for the exception type; you may prefer to allow descendant types as well.

Update 2012-11-29: Opened a UserVoice suggestion to add this to Visual Studio.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • This works fine in other versions of C#, but has been removed from Windows 8 metro apps http://blogs.msdn.com/b/visualstudioalm/archive/2012/06/18/visual-studio-2012-rc-what-s-new-in-unit-testing.aspx – Peregrine Oct 11 '12 at 10:48
  • Well, that is "interesting"! I've updated my answer to show how you can implement an `async`-compatible `ThrowsException` if the Win8 unit tests don't support it. – Stephen Cleary Oct 11 '12 at 11:02
  • Thanks Stephen that does work, although I can't believe that what seems like such an elegant new feature would require such a hack, especially seeing as Windows 8 Metro places such an emphasis on async methods. Hopefully the final documentation following Windows 8 release will be more forthcoming. – Peregrine Oct 11 '12 at 11:23
  • 1
    I guess the [user voice was heard](https://stackoverflow.com/a/63520581/542251) – Liam Aug 21 '20 at 09:54
7
[TestMethod]
public void DivideTest1()
{
    Func<Task> action = async () => { int Result = await AsyncMathsStatic.Divide(4, 0); });
    action.ShouldThrow<DivideByZeroException>();
}

Using .ShouldThrow() from FluentAssertions nuget package works for me

msrc
  • 663
  • 1
  • 9
  • 19
3

With the addition of the ThrowsExceptionAsync method, this is now covered natively without the need for third parties or extension methods in MSTest:

await Assert.ThrowsExceptionAsync<Exception>(() => { Fail(); });
Liam
  • 27,717
  • 28
  • 128
  • 190
2

This works for me

    public async Task TestMachineAuthBadJson() {
        // Arrange

        // act
        DocsException ex = await Assert.ThrowsExceptionAsync<DocsException>(() => MachineAuth.GetToken());
        //assert
        StringAssert.Contains(ex.Message, "DOCS-API error: ");

        }
2

This is old question, but I stumbled upon this problem right now and decided to give updated answer to that question.

Xuint now supports async exceptions tests with Assert.ThrowsAsync method.

Michał Turczyn
  • 32,028
  • 14
  • 47
  • 69
1

I ran into a similar issue a few days ago and ended up creating something similar to Stephen's answer above. It's available as a Gist. Hopefully it's of help - the github gist at has the full code and a sample usage.

/// <summary>
/// Async Asserts use with Microsoft.VisualStudio.TestPlatform.UnitTestFramework
/// </summary>
public static class AsyncAsserts
{
    /// <summary>
    /// Verifies that an exception of type <typeparamref name="T"/> is thrown when async<paramref name="func"/> is executed.
    /// The assertion fails if no exception is thrown
    /// </summary>
    /// <typeparam name="T">The generic exception which is expected to be thrown</typeparam>
    /// <param name="func">The async Func which is expected to throw an exception</param>
    /// <returns>The task object representing the asynchronous operation.</returns>
    public static async Task<T> ThrowsException<T>(Func<Task> func) where T : Exception
    {
        return await ThrowsException<T>(func, null);
    }

    /// <summary>
    /// Verifies that an exception of type <typeparamref name="T"/> is thrown when async<paramref name="func"/> is executed.
    /// The assertion fails if no exception is thrown
    /// </summary>
    /// <typeparam name="T">The generic exception which is expected to be thrown</typeparam>
    /// <param name="func">The async Func which is expected to throw an exception</param>
    /// <param name="message">A message to display if the assertion fails. This message can be seen in the unit test results.</param>
    /// <returns>The task object representing the asynchronous operation.</returns>
    public static async Task<T> ThrowsException<T>(Func<Task> func, string message) where T : Exception
    {
        if (func == null)
        {
            throw new ArgumentNullException("func");
        }

        string failureMessage;
        try
        {
            await func();
        }
        catch (Exception exception)
        {
            if (!typeof(T).Equals(exception.GetType()))
            {
                // "Threw exception {2}, but exception {1} was expected. {0}\nException Message: {3}\nStack Trace: {4}"
                failureMessage = string.Format(
                    CultureInfo.CurrentCulture,
                    FrameworkMessages.WrongExceptionThrown,
                    message ?? string.Empty,
                    typeof(T),
                    exception.GetType().Name,
                    exception.Message,
                    exception.StackTrace);

                Fail(failureMessage);
            }
            else
            {
                return (T)exception;
            }
        }

        // "No exception thrown. {1} exception was expected. {0}"
        failureMessage = string.Format(
                    CultureInfo.CurrentCulture,
                    FrameworkMessages.NoExceptionThrown,
                    message ?? string.Empty,
                    typeof(T));

        Fail(failureMessage);
        return default(T);
    }

    private static void Fail(string message, [CallerMemberName] string assertionName = null)
    {
        string failureMessage = string.Format(
            CultureInfo.CurrentCulture,
            FrameworkMessages.AssertionFailed,
            assertionName,
            message);

        throw new AssertFailedException(failureMessage);
    }
}
Joseph Daigle
  • 47,650
  • 10
  • 49
  • 73
0

Support for using async lambdas inside a ThrowsException method has since been added in Visual Studio 2012 Update 2, but only for Windows Store test projects.

The one gotcha is that you need to use the Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer.Assert class to call ThrowsException.

So, to use the new ThrowsException method, you could do something like this:

using AsyncAssert = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer.Assert;

[TestMethod]
public void DivideTest1()
{
    AsyncAssert.ThrowsException<DivideByZeroException>(async () => { 
        int Result = await AsyncMathsStatic.Divide(4, 0); });
}
Sean Clifford
  • 156
  • 2
  • 3
  • 8