如何对这个正确抛出异常的异步方法进行单元测试?

13

我在一个接口中有以下方法...

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

效果很棒。

现在我正在尝试编写一个单元测试来测试当出现问题时 - 代码会抛出异常。

在这种情况下,我已经设置了我的方法来抛出一个 HttpRequestException。 我的单元测试未能说明我抛出了该异常..

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

单元测试的错误信息为:

Shouldly.ChuckedAWobbly
var result = Should
throw
System.Net.Http.HttpRequestException
but does not

因此,断言框架的意思是:您期望抛出异常,但没有抛出任何异常。

当我通过代码进行逐步调试时,异常100%被抛出。

请问有谁能发现我的单元测试代码哪里出了问题吗?


异常是否被中间某个地方捕获了? - elnigno
这个回答解决了你的问题吗?为特定异常单元测试异步方法 - Liam
6个回答

14

问题在于你的断言框架不了解异步方法。我建议你向他们提出问题。

同时,你可以使用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));

2
请注意,自 2014 年 2 月起,任务重载已移至另一个文件。https://github.com/shouldly/shouldly/blob/master/src/Shouldly/ShouldThrowTaskExtensions.cs#L10Shouldly 会阻塞测试,因此测试不需要等待 Should.Throw 方法。我认为我们将添加 Should.ThrowAsync,它是异步的。 - Jake Ginnivan

4

像这样测试:

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

或者:

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

否则,你的Should.Throw会在async lambda完成之前返回。

第一行无法编译。 - Pure.Krome
@Pure.Krome,据我所知,它表示Should.Throw期望一个void lambda。第二个也是这样做的,而不会窥视结果。 - avo
4
ResultWait都将异常封装到AggregateException中,因此这个答案不能直接使用。 - Stephen Cleary
1
如果在此处设置了同步上下文,这也可能导致死锁,因为您正在同步地阻塞异步方法。 - Servy

3
问题在于传递的lambda返回一个任务。如果不等待该任务完成,Should.Throw只能观察到抛出的异常。作为解决方法,您可以自己.Wait搜索异步返回的任务。
自Visual Studio 2012以来,mstest(内置的Visual Studio测试框架)支持异步测试。您可以通过将测试方法声明中的“void”替换为“async Task”来进行更改。
[TestMethod]
[ExpectedException(typeof(System.Net.Http.HttpRequestException))]
public async Task SomeTest()
{
   await service.SearchAsync(searchOptions);
}

你可能在使用不同的单元测试框架,但是目前不清楚你使用的是哪个。请查阅其文档以查看它是否支持异步测试。
另外,NUnit 2.6.3 似乎也支持异步测试。 编辑: 所以你正在使用 xUnit。这个特定问题已经在 xUnit 2.0 中得到了修复。不过目前仍处于 alpha 版本。

1
传递的lambda实际上返回void,从async void方法中捕获异常非常困难。 Wait将其异常包装在AggregateException中,因此无法使用。 - Stephen Cleary

3

2

该异常是在不同的线程上抛出的,而不是您的单元测试运行的线程。单元测试框架只能预期其自己线程上的异常。

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


那么,你是在说 async/await 不起作用,异常没有传递过来,我无法测试这个东西? - Pure.Krome
但是...没有同步方法。 - Pure.Krome
不,那是正常行为。这可能为异步任务的异常处理提供一些上下文 http://msdn.microsoft.com/en-us/magazine/jj991977.aspx - Chris Ballard

1
今天的Shouldly包含Should.ThrowAsync<T>功能。
您可以像这样使用它:
await Should.ThrowAsync<HttpRequestException>(() => service.SearchAsync(searchOptions));

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