【问题标题】:How can I unit test this async method which (correctly) throws an exception?如何对这个(正确)抛出异常的异步方法进行单元测试?
【发布时间】:2014-04-19 14:36:23
【问题描述】:

我在一个界面中有以下方法..

Task<SearchResult<T>> SearchAsync(TU searchOptions);

效果很好。

现在我正在尝试进行单元测试以测试何时出现问题 - 并且代码会引发异常。

在这种情况下,我设置了我的方法来抛出一个HttpRequestException。我的单元测试没有说我抛出了那个异常..

var result = Should.Throw<HttpRequestException>
    (async () => await service.SearchAsync(searchOptions));

单元测试的错误信息是

应该.ChuckedAWobbly
var 结果 = 应该

System.Net.Http.HttpRequestException
但没有

所以assertion framework 是在说:你已经预料到了一个异常,但没有抛出任何异常。

当我单步执行代码时,会 100% 抛出异常。

谁能看看我的单元测试代码做错了什么?

【问题讨论】:

标签: c# .net unit-testing async-await


【解决方案1】:

今天应该包含Should.ThrowAsync&lt;T&gt;

你可以这样使用它:

await Should.ThrowAsync<HttpRequestException>(() => service.SearchAsync(searchOptions));

【讨论】:

    【解决方案2】:

    问题是传递的 lambda 返回一个任务。抛出的异常只能由Should.Throw 观察到,如果它等待此任务完成,显然它不会。作为一种变通方法,您可以在 SearchAsync 自己返回的任务上 .Wait

    mstest(内置的 Visual Studio 测试框架)自 Visual Studio 2012 起支持异步测试。您基本上可以通过将“void”替换为“async Task”来更改测试方法声明。

    [TestMethod]
    [ExpectedException(typeof(System.Net.Http.HttpRequestException))]
    public async Task SomeTest()
    {
       await service.SearchAsync(searchOptions);
    }
    

    您可能正在使用不同的单元测试框架,但不清楚是哪一个。请查阅其文档以查看它是否支持异步测试。

    NUnit 2.6.3 似乎也支持异步测试。

    edit:所以您使用的是 xUnit。这个特殊问题是fixed for xUnit 2.0。不过,它目前仍处于 alpha 阶段。

    【讨论】:

    • 传递的 lambda 实际上返回void,并且很难从async void 方法中捕获异常。 Wait 将其异常包装在 AggregateException 中,所以它不起作用。
    【解决方案3】:

    问题是您的断言框架不理解异步方法。我建议您向他们提出问题。

    同时,您可以使用source for Should.Throw 编写自己的MyShould.ThrowAsync

    public static async Task<TException> ThrowAsync<TException>(Func<Task> actual)
        where TException : Exception
    {
      try
      {
        await actual();
      }
      catch (TException e)
      {
        return e;
      }
      catch (Exception e)
      {
        throw new ChuckedAWobbly(new ShouldlyMessage(typeof(TException), e.GetType()).ToString());
      }
    
      throw new ChuckedAWobbly(new ShouldlyMessage(typeof(TException)).ToString());
    }
    

    并像这样使用它:

    var result = await MyShould.ThrowAsync<HttpRequestException>
        (async () => await service.SearchAsync(searchOptions));
    

    或稍微简单和等效的:

    var result = await MyShould.ThrowAsync<HttpRequestException>
        (() => service.SearchAsync(searchOptions));
    

    【讨论】:

    • 仅供参考,自 v2(2014 年 2 月)以来,任务重载位于另一个文件中。 github.com/shouldly/shouldly/blob/master/src/Shouldly/… Shouldly 确实会阻塞,因此测试不需要等待 Should.Throw 方法。我想我们会添加 Should.ThrowAsync 虽然它是异步的
    【解决方案4】:

    像这样测试它:

    var result = Should.Throw<HttpRequestException>
        (() => service.SearchAsync(searchOptions).Result);
    

    或者:

    var result = Should.Throw<HttpRequestException>
        (() => service.SearchAsync(searchOptions).Wait());
    

    否则,您的 Should.Throwasync lambda 完成之前返回。

    【讨论】:

    • 第一行无法编译。
    • @Pure.Krome,这意味着Should.Throw 需要一个void lambda,据我所知。第二个做同样的事情,但没有查看结果。
    • ResultWait 都将异常包装到 AggregateException,所以这个答案不会按原样工作。
    • 如果此处设置了同步上下文,这也可能导致死锁,因为您正在同步阻塞异步方法。
    【解决方案5】:

    对异步代码/功能进行单元测试非常困难。我自己正在进行单元测试异步并遇到与您相同的问题。

    我发现以下两个资源非常有用:

    【讨论】:

      【解决方案6】:

      异常是在与运行单元测试的线程不同的线程上引发的。单元测试框架只能在自己的线程上预测异常。

      我建议您在服务的同步版本上测试异常。

      【讨论】:

      • 那么——你是说async/await 不工作,异常没有通过,我不能测试这个东西?
      • 但是..没有同步方法。
      • 不,这是正常行为。这可能会为异步任务的异常处理提供一些上下文msdn.microsoft.com/en-us/magazine/jj991977.aspx
      猜你喜欢
      • 2018-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-01
      • 1970-01-01
      • 2013-03-15
      • 1970-01-01
      相关资源
      最近更新 更多