2013-03-26 2 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>()); 
} 

Результат Test Failed

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

Результат Failed Тест

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

вы должны добавить больше коды из общедоступных задач Get (строка domainUserName), как все ваших тестов для domainUser = «Foo» и единственной ошибки, которые вы показали, это для пустой domainUser (или null) – JleruOHeP

+0

@JleruOHeP - thanks -got увлекся немного. Изменили код. –

+0

После ваших изменений все тестовые примеры все те же? И случай 1 все еще не вызывает никакого исключения? – JleruOHeP

ответ

38

Вы видите проблемы, связанные с async void.

В частности:

1) async() => await userController.Get("foo") превращается в TestDelegate, который возвращает void, поэтому ваше лямбда-выражение трактуется как async void. Таким образом, тестовый бегун начнет выполнение лямбды, но не дожидаясь его завершения. Лямбда возвращается до завершения Get (потому что это async), и тестировщик видит, что он вернулся без исключения.

2) Wait обертывает любые исключения в AggregateException.

3) Опять же, лямбда async обрабатывается как async void, поэтому испытательный бегун не ждет его завершения.

4) Я рекомендую вам сделать это async Task, а не async void, но в этом случае тестовый бегун ждет завершения и, таким образом, видит исключение.

Согласно this bug report, есть исправление для этого, следующего в следующем сборке NUnit. В то же время вы можете создать свой собственный метод ThrowsAsync; a example for xUnit is here.

+0

Спасибо - я подозревал ошибку, рад, что она подтверждена. Я буду использовать подход «ThrowsAsync», выглядит намного чище, чем у меня сейчас. –

+0

Ваш подход работает хорошо, мне пришлось расширить вашу идею ThrowsAsync, чтобы добавить функцию assert, но это было не слишком сложно. Я обновил свой ответ ниже тем, что у меня есть сейчас. Еще раз спасибо. –

+1

Ошибка исправлена ​​с 2.6.3 – DalSoft

2

Если вы ожидаете задачи, то Исключения, которые были выбраны, агрегируются в AggregateException. Вы можете проверить внутренние исключения из AggregateException. Это может быть причиной того, что ваш случай 2 не работает.

Необработанные исключения, которые вызывают код пользователя, который запускается внутри задачи, распространяются обратно на соединительный поток, за исключением определенных сценариев, описанных далее в этом разделе. Исключения распространяются при использовании одного из статических методов или экземпляров Task.Wait или Task.Wait, и вы обрабатываете их, закрывая вызов в заявлении try-catch. Если задание является родительским элементом связанных дочерних задач или если вы ожидаете нескольких задач, может быть выбрано несколько исключений. Чтобы распространить все исключения обратно на вызывающий поток, инфраструктура Task обертывает их в экземпляр AggregateException. Свойство AggregateException имеет свойство InnerExceptions, которое можно перечислить, чтобы просмотреть все исходные исключения, которые были выбраны, и обрабатывать (или не обрабатывать) каждый отдельно. Даже если выбрано только одно исключение, оно все еще завернуто в исключение AggregateException.

Link to MSDN

+0

Да, это правда. Я не хотел заглядывать в 'AggregateException', чтобы проверить, вызывается ли« HttpResponseException », но похоже, что нет возможности? –

+0

Я не думаю, что есть способ взглянуть на AggregateException, но я думаю, что это не так уж плохо. – roqz

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

Я не слишком доволен, но это работает.

EDIT 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 –

+0

Фу, я рад, что не остановил прокрутку :) –

+0

При использовании Assert.ThrowsAsync <> Я не думаю, что ваш тест должен быть асинхронным (в данном случае). Просто сделайте его недействительным. – nashwan

Смежные вопросы