2013-03-26 108 views
32

我有一个控制器UserController这个动作NUnit的异步测试例外断言

// GET /blah 
public Task<User> Get(string domainUserName) 
{ 
     if (string.IsNullOrEmpty(domainUserName)) 
     { 
      throw new ArgumentException("No username specified."); 
     } 

     return Task.Factory.StartNew(
      () => 
       { 
        var user = userRepository.GetByUserName(domainUserName); 
        if (user != null) 
        { 
         return user; 
        } 

        throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, string.Format("{0} - username does not exist", domainUserName))); 
       }); 
} 

我想写的,我抛出一个异常404的情况下进行测试。

这是我曾尝试与输出 -

1)

[Test] 
public void someTest() 
{ 
     var mockUserRepository = new Mock<IUserRepository>(); 
     mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
    var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

    Assert.That(async() => await userController.Get("foo"), Throws.InstanceOf<HttpResponseException>()); 
} 

结果 测试失败

Expected: instance of <System.Web.Http.HttpResponseException> 
    But was: no exception thrown 

2)

[Test] 
public void someTest() 
{ 
     var mockUserRepository = new Mock<IUserRepository>(); 
     mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
    var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

    var httpResponseException = Assert.Throws<HttpResponseException>(() => userController.Get("foo").Wait()); 
    Assert.That(httpResponseException.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); 
} 

结果 测试失败

Expected: <System.Web.Http.HttpResponseException> 
    But was: <System.AggregateException> (One or more errors occurred.) 

3)

[Test] 
public void someTest() 
{ 
     var mockUserRepository = new Mock<IUserRepository>(); 
     mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
    var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

    var httpResponseException = Assert.Throws<HttpResponseException>(async() => await userController.Get("foo")); 
    Assert.That(httpResponseException.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); 
} 

结果 测试失败

Expected: <System.Web.Http.HttpResponseException> 
    But was: null 

4)

[Test] 
[ExpectedException(typeof(HttpResponseException))] 
public async void ShouldThrow404WhenNotFound() 
{   var mockUserRepository = new Mock<IUserRepository>(); 
     mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 

    var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

    var task = await userController.Get("foo"); 
} 

结果 测试通过

问题 -

  1. 为什么Assert.Throws不能处理HttpResponseException时的ExpectedException呢?
  2. 我不想仅仅测试引发的异常。我想在响应的状态代码上声明。有什么办法做到这一点?

对这些行为及其原因的任何比较都会很棒!

+0

您应该从公共任务获取(串domainUserName)的所有测试都是domainUser =“foo”和你表现是唯一的错误添加更多的代码为空的domainUser(或null) – JleruOHeP 2013-03-26 10:29:51

+0

@JleruOHeP - 谢谢-got带走了一点。编辑过代码。 – 2013-03-26 10:45:51

+0

编辑完成后,所有的测试用例仍然是一样的吗?案例1仍然没有抛出任何异常? – JleruOHeP 2013-03-26 11:49:25

回答

38

你所看到的问题,是由于async void

特别是:

1)async() => await userController.Get("foo")被转换成TestDelegate,它返回void,所以你lambda表达式作为async void处理。所以测试运行者将开始执行lambda,但不会等待它完成。在Get完成之前,lambda返回(因为它是async),并且测试运行程序发现它没有异常返回。

2)WaitAggregateException中包含任何例外。

3)同样,async lambda被视为async void,所以测试运行者不会等待其完成。

4)我建议你制作async Task而不是async void,但在这种情况下,测试运行器会等待完成,从而发现异常。

根据this bug report,在NUnit的下一个版本中会有一个修复。同时,您可以建立自己的ThrowsAsync方法;一个example for xUnit is here

+0

谢谢 - 我怀疑有一个bug,很高兴它已被证实。我将使用'ThrowsAsync'方法,看起来比我现在的要干净得多。 – 2013-03-26 13:09:19

+0

您的方法运作良好,我不得不延长您的ThrowsAsync想法以添加断言功能,但这并不太困难。我已经用我现在所拥有的更新了我的答案。再次感谢。 – 2013-03-27 05:12:15

+1

自2.6.3以来已修复错误 – DalSoft 2014-06-09 17:01:56

2

如果您等待任务,则引发的异常将聚合到AggregateException中。您可以检查AggregateException的内部异常。这可能是你案例2不起作用的原因。

在任务中运行的用户代码抛出的未处理的异常会传播回加入的线程,本主题后面介绍的某些情况除外。当您使用静态或实例Task.Wait或Task.Wait方法之一时会传播异常,并通过将该调用放入try-catch语句中来处理它们。如果任务是附加的子任务的父级,或者您正在等待多个任务,则可能会抛出多个异常。为了将所有异常传播回调用线程,Task基础结构将它们包装在一个AggregateException实例中。 AggregateException有一个InnerExceptions属性,可以通过枚举来检查所有引发的原始异常,并分别处理(或不处理)每个异常。即使只抛出一个异常,它仍然包装在一个AggregateException中。

Link to MSDN

+0

是的,没错。我不想查看'AggregateException'来检查是否抛出了'HttpResponseException',但看起来好像没有选项? – 2013-03-26 12:10:48

+0

我不认为有一种方法可以查看AggregateException,但我认为这种方式并不算太坏。 – roqz 2013-03-26 12:15:12

11

This blog约与我相似的问题举行了会谈。

我跟着那里提出的建议,并有一个这样的试验 -

[Test] 
    public void ShouldThrow404WhenNotFound() 
    { 
     var mockUserRepository = new Mock<IUserRepository>(); 
     mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
     var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

     var aggregateException = Assert.Throws<AggregateException>(() => userController.Get("foo").Wait()); 
     var httpResponseException = aggregateException.InnerExceptions 
      .FirstOrDefault(x => x.GetType() == typeof(HttpResponseException)) as HttpResponseException; 

     Assert.That(httpResponseException, Is.Not.Null); 
     Assert.That(httpResponseException.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); 
    } 

我不太高兴,但是,这个工作。

编辑1

通过@StephenCleary的启发,我添加了一个静态辅助的类,它的断言,我期待的。它看起来像这样 -

public static class AssertEx 
{ 
    public static async Task ThrowsAsync<TException>(Func<Task> func) where TException : class 
    { 
     await ThrowsAsync<TException>(func, exception => { }); 
    } 

    public static async Task ThrowsAsync<TException>(Func<Task> func, Action<TException> action) where TException : class 
    { 
     var exception = default(TException); 
     var expected = typeof(TException); 
     Type actual = null; 
     try 
     { 
      await func(); 
     } 
     catch (Exception e) 
     { 
      exception = e as TException; 
      actual = e.GetType(); 
     } 

     Assert.AreEqual(expected, actual); 
     action(exception); 
    } 
} 

我现在能有这样一个试验 -

[Test] 
    public async void ShouldThrow404WhenNotFound() 
    { 
     var mockUserRepository = new Mock<IUserRepository>(); 
     mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
     var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

     Action<HttpResponseException> asserts = exception => Assert.That(exception.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); 
     await AssertEx.ThrowsAsync(() => userController.Get("foo"), asserts); 
    } 
19

我不知道的时候加入,但的NUnit(3.4.1在写作时)的最新版本包括ThrowsAsync方法

看到https://github.com/nunit/docs/wiki/Assert.ThrowsAsync

我没有测试过这个例子具体而言,但它应该像这样工作:

[Test] 
public async void ShouldThrow404WhenNotFound() 
{ 
    var mockUserRepository = new Mock<IUserRepository>(); 
    mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
    var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

    var exception = Assert.ThrowsAsync<HttpResponseException>(() => userController.Get("foo")); 

    Assert.That(exception.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); 
} 
+4

此答案应位于顶部,以便人们不浪费时间尝试所有定制的解决方案。答案已经立即内置在NUnit的 – 2016-12-22 18:37:44

+0

呼,我很高兴我没有停止向下滚动:) – 2016-12-23 01:14:40

+0

当使用Assert.ThrowsAsync <>我不认为你的测试必须是异步(在这种情况下)。只要使其无效。 – nashwan 2017-12-11 16:41:38