针对特定异常的异步方法单元测试

22

有人有在Windows 8 Metro应用程序中单元测试异步方法的示例吗?以确保它抛出所需的异常?

给定一个具有异步方法的类。

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;
    }
}

我想使用新的Async.ExpectsException结构编写测试方法。我已经尝试过:-

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

但是测试当然不会等待异步方法完成,因此导致测试失败,即异常没有被抛出。

7个回答

22

您可以使用一个带有常规 ExpectedExceptionAttributeasync Task 单元测试:

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

评论更新: 在Win8单元测试项目中,ExpectedExceptionAttribute已被Assert.ThrowsException取代,这在我看来是一个良好的设计变更,但我不知道为什么它只支持Win8。

假设没有与async兼容的Assert.ThrowsException(由于缺乏文档,我无法确定是否有这样的函数),您可以自己构建一个:

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 ...");
  }
}

然后将其用作这样:
[TestMethod]
public async Task DivideTest1()
{
  await AssertEx.ThrowsException<DivideByZeroException>(async () => { 
      int Result = await AsyncMathsStatic.Divide(4, 0);
  });
}

请注意,这里的示例仅对异常类型进行了精确检查;您可能更喜欢允许派生类型。
更新于2012-11-29:已提出UserVoice建议以将其添加到Visual Studio。

这在其他版本的C#中运行良好,但已从Windows 8 Metro应用程序中删除。http://blogs.msdn.com/b/visualstudioalm/archive/2012/06/18/visual-studio-2012-rc-what-s-new-in-unit-testing.aspx - Peregrine
哦,这真是“有趣”!我已经更新了我的答案,展示了如何实现一个async兼容的ThrowsException,如果Win8单元测试不支持它的话。 - Stephen Cleary
谢谢Stephen,这确实有效,尽管我无法相信一个看起来如此优雅的新功能需要这样的黑客攻击,特别是在Windows 8 Metro强调异步方法的情况下。希望在Windows 8发布后的最终文档中会更加详尽。 - Peregrine
1
我猜测用户的声音被听到了 - Liam

8
[TestMethod]
public void DivideTest1()
{
    Func<Task> action = async () => { int Result = await AsyncMathsStatic.Divide(4, 0); });
    action.ShouldThrow<DivideByZeroException>();
}

使用FluentAssertions nuget包中的.ShouldThrow()对我很有效。

4

通过添加ThrowsExceptionAsync方法,MSTest现在已经原生覆盖了此功能,无需第三方或扩展方法:

await Assert.ThrowsExceptionAsync<Exception>(() => { Fail(); });

这应该是最佳答案! 注意:如果Fail()不是void类型,则以下语法可行: await Assert.ThrowsExceptionAsync<Exception>(() => { return Fail(); }); - undefined

2

这对我有效

    public async Task TestMachineAuthBadJson() {
        // Arrange

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

        }

2

虽然这是一个旧问题,但我现在遇到了这个问题,并决定对该问题提供更新的答案。

Xuint现在支持使用Assert.ThrowsAsync方法进行异步异常测试。


值得一提的是,它应该被等待。例如:await Assert.ThrowsAsync... - Ε Г И І И О

1
几天前我遇到了类似的问题,最终创建了与Stephen上面回答类似的东西。它作为Gist可用。希望它能有所帮助 - github gist包含完整的代码和示例用法。
/// <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);
    }
}

0

Visual Studio 2012 Update 2已经支持在ThrowsException方法中使用异步lambda表达式, 但仅适用于Windows Store测试项目。

需要注意的是,您需要使用Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer.Assert类来调用ThrowsException

因此,要使用新的ThrowsException方法,您可以像这样操作:

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接