Как я могу использовать Assert.Throws, чтобы утверждать тип исключения?


247

Как мне использовать, Assert.Throwsчтобы утверждать тип исключения и фактическую формулировку сообщения.

Что-то вроде этого:

Assert.Throws<Exception>(
    ()=>user.MakeUserActive()).WithMessage("Actual exception message")

Метод, который я тестирую, выдает несколько сообщений одного типа с разными сообщениями, и мне нужен способ проверить, что правильное сообщение выдается в зависимости от контекста.

Ответы:


444

Assert.Throws возвращает выброшенное исключение, которое позволяет вам утверждать об исключении.

var ex = Assert.Throws<Exception>(() => user.MakeUserActive());
Assert.That(ex.Message, Is.EqualTo("Actual exception message"));

Таким образом, если не сгенерировано исключение или сгенерировано исключение неправильного типа, первое Assert.Throwsутверждение не будет выполнено. Однако, если выдается исключение правильного типа, теперь вы можете утверждать фактическое исключение, которое вы сохранили в переменной.

Используя этот шаблон, вы можете утверждать что-то другое, кроме сообщения об исключении, например, в случае ArgumentExceptionи производных, вы можете утверждать, что имя параметра является правильным:

var ex = Assert.Throws<ArgumentNullException>(() => foo.Bar(null));
Assert.That(ex.ParamName, Is.EqualTo("bar"));

Вы также можете использовать свободный API для выполнения этих утверждений:

Assert.That(() => foo.Bar(null), 
Throws.Exception
  .TypeOf<ArgumentNullException>()
  .With.Property("ParamName")
  .EqualTo("bar"));

или альтернативно

Assert.That(
    Assert.Throws<ArgumentNullException>(() =>
        foo.Bar(null)
    .ParamName,
Is.EqualTo("bar"));

Небольшой совет при утверждении сообщений об исключении состоит в том, чтобы украсить тестовый метод, SetCultureAttributeчтобы убедиться, что выброшенное сообщение использует ожидаемую культуру. Это вступает в игру, если вы храните свои сообщения об исключениях в качестве ресурсов, позволяющих локализовать.


Это было очень полезно для меня - я хотел отобразить ошибку, я даже не читал, было ли возвращено значение методом Assert.Throws. Спасибо
Haroon

6
+1 Спасибо за показ Fluent API, по какой-то причине у меня возникли проблемы с пониманием, как использовать его только из одних документов NUnit.
aolszowka

Если вы хотите подтвердить сообщение, вы также можете напрямую использовать свойство Message вместо «Свойство».
Марсель

25

Теперь вы можете использовать ExpectedExceptionатрибуты, например,

[Test]
[ExpectedException(typeof(InvalidOperationException), 
 ExpectedMessage="You can't do that!"]
public void MethodA_WithNull_ThrowsInvalidOperationException()
{
    MethodA(null);
}

2
Когда меня впервые увидели, это немного смутило меня, потому что в тесте не было никаких подтверждений, которые были для меня запахом. Это хорошая особенность, но в команде следует обсудить, следует ли этому атрибуту привыкать к Assert.Throws
Marcel

14
+1 также хороший способ проверить исключения. Единственное, о чем следует помнить, это то, что теоретически любая строка кода, генерирующая исключение InvalidOperationException с этим сообщением, пройдет тест, включая код в вашем тесте, который подготавливает тестовые данные / объекты, или любой другой метод, который вам может потребоваться выполнить до тот, который вы заинтересованы в тестировании, возможно, приведет к ложному положительному результату. Конечно, это зависит от того, насколько конкретным является сообщение и тип исключения, которое вы тестируете. С помощью Assert.Throwвы можете выбрать точную линию, которая вас интересует.
Нет,

21
Атрибут ExpectedException устарел в NUnit 3: github.com/nunit/docs/wiki/Breaking-Changes
Франк Себастьян,

13
Assert.That(myTestDelegate, Throws.ArgumentException
    .With.Property("Message").EqualTo("your argument is invalid."));

2
С введением оператора nameof я отредактировал бы этот превосходный ответ:Assert.That(myTestDelegate, Throws.ArgumentException .With.Property(nameof(ArgumentException.Message)).EqualTo("your argument is invalid."));
Сэмюэль

@Samuel Это редактирование будет использовать строго типизированную ссылку, что приятно, но, с другой стороны, это имя свойства с чрезвычайно низким отток и волшебная строка улучшает беглость. Я полагаю, дело вкуса
Джордан Моррис

1
Я полностью согласен с вами в отношении Exception.Message. Я по-прежнему рекомендовал бы, по крайней мере, добавить эту альтернативу, потому что она With.Propertyможет быть использована и для других объектов, которые в этом случае улучшат стабильность кода.
Самуил

5

Это старый, но актуальный вопрос с устаревшими ответами, поэтому я добавляю текущее решение:

public void Test() {
    throw new MyCustomException("You can't do that!");
}

[TestMethod]
public void ThisWillPassIfExceptionThrown()
{
    var exception = Assert.ThrowsException<MyCustomException>(
        () => Test(),
        "This should have thrown!");
    Assert.AreEqual("You can't do that!", exception.Message);
}

Это работает с using Microsoft.VisualStudio.TestTools.UnitTesting;


Я был очень удивлен, что не было четкого способа утверждать, что метод генерирует исключение, как в JUnit. Если нет последствий, о которых я не знаю, это, вероятно, самый актуальный ответ в настоящее время.
NetherGranite

3

Чтобы расширить ответ персистента и предоставить больше возможностей NUnit, вы можете сделать это:

public bool AssertThrows<TException>(
    Action action,
    Func<TException, bool> exceptionCondition = null)
    where TException : Exception 
{
    try
    {
        action();
    }
    catch (TException ex)
    {
        if (exceptionCondition != null)
        {
            return exceptionCondition(ex);
        }

        return true;
    }
    catch
    {
        return false;
    }

    return false; 
}

Примеры:

// No exception thrown - test fails.
Assert.IsTrue(
    AssertThrows<InvalidOperationException>(
        () => {}));

// Wrong exception thrown - test fails.
Assert.IsTrue(
    AssertThrows<InvalidOperationException>(
        () => { throw new ApplicationException(); }));

// Correct exception thrown - test passes.
Assert.IsTrue(
    AssertThrows<InvalidOperationException>(
        () => { throw new InvalidOperationException(); }));

// Correct exception thrown, but wrong message - test fails.
Assert.IsTrue(
    AssertThrows<InvalidOperationException>(
        () => { throw new InvalidOperationException("ABCD"); },
        ex => ex.Message == "1234"));

// Correct exception thrown, with correct message - test passes.
Assert.IsTrue(
    AssertThrows<InvalidOperationException>(
        () => { throw new InvalidOperationException("1234"); },
        ex => ex.Message == "1234"));

2

Я понимаю, что прошло много времени с тех пор, как эта проблема поднималась, но недавно я столкнулся с тем же и предложил эту функцию для MSTest:

public bool AssertThrows(Action action) where T : Exception 
{ 
try {action();} 
catch(Exception exception) 
{ 
    if (exception.GetType() == typeof(T)) return true; 
} 
return false; 
}

использование:

Assert.IsTrue(AssertThrows<FormatException>(delegate{ newMyMethod(MyParameter); }));

Подробнее здесь: http://phejndorf.wordpress.com/2011/02/21/assert-that-a-particular-exception-has-occured/


2

Так как меня беспокоит многословие некоторых новых шаблонов NUnit, я использую что-то вроде этого, чтобы создать код, который будет чище для меня лично:

public void AssertBusinessRuleException(TestDelegate code, string expectedMessage)
{
    var ex = Assert.Throws<BusinessRuleException>(code);
    Assert.AreEqual(ex.Message, expectedMessage);
}

public void AssertException<T>(TestDelegate code, string expectedMessage) where T:Exception
{
    var ex = Assert.Throws<T>(code);
    Assert.AreEqual(ex.Message, expectedMessage);
}

Использование тогда:

AssertBusinessRuleException(() => user.MakeUserActive(), "Actual exception message");

1
Что такое TestDelegate?
reggaeguitar

1
Это позволяет передавать код для выполнения в качестве параметра. Это класс в платформе NUnit (v3.2.0.0).
Savage
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.