Jak korzystać z Assert.Throws, aby potwierdzić typ wyjątku?

247

Jak użyć, Assert.Throwsaby potwierdzić typ wyjątku i faktyczne sformułowanie wiadomości.

Coś takiego:

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

Metoda, którą testuję, rzuca wiele wiadomości tego samego typu, z różnymi wiadomościami, i potrzebuję sposobu na sprawdzenie, czy poprawna wiadomość jest generowana w zależności od kontekstu.

epitka
źródło

Odpowiedzi:

444

Assert.Throws zwraca zgłoszony wyjątek, który pozwala potwierdzić ten wyjątek.

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

Jeśli więc nie zostanie zgłoszony żaden wyjątek lub zgłoszony zostanie wyjątek niewłaściwego typu, pierwsze Assert.Throwspotwierdzenie zakończy się niepowodzeniem. Jeśli jednak zostanie zgłoszony wyjątek odpowiedniego typu, możesz teraz potwierdzić faktyczny wyjątek zapisany w zmiennej.

Korzystając z tego wzorca, możesz potwierdzić inne rzeczy niż komunikat wyjątku, np. W przypadku ArgumentExceptioni pochodnych możesz potwierdzić, że nazwa parametru jest poprawna:

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

Możesz również użyć płynnego interfejsu API do wykonywania tych zapewnień:

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

lub alternatywnie

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

Mała wskazówka przy potwierdzaniu komunikatów o wyjątkach polega na udekorowaniu metody testowej, SetCultureAttributeaby upewnić się, że komunikat o błędzie używa oczekiwanej kultury. Ma to zastosowanie w przypadku przechowywania wiadomości wyjątków jako zasobów umożliwiających lokalizację.

Patrik Hägne
źródło
To było dla mnie bardzo pomocne - chciałem wyświetlić błąd, nawet nie przeczytałem, czy metoda została zwrócona przez metodę Assert.Throws. Dzięki
Haroon
6
+1 Dziękujemy za pokazanie płynnego interfejsu API, z jakiegoś powodu miałem problemy ze zrozumieniem, jak go używać tylko z samych dokumentów NUnit.
aolszowka
Jeśli chcesz potwierdzić wiadomość, możesz również bezpośrednio użyć właściwości Wiadomość zamiast „Właściwości”.
Marcel
25

Możesz teraz używać ExpectedExceptionatrybutów, np

[Test]
[ExpectedException(typeof(InvalidOperationException), 
 ExpectedMessage="You can't do that!"]
public void MethodA_WithNull_ThrowsInvalidOperationException()
{
    MethodA(null);
}
Jackson Pope
źródło
2
Niepokoiło mnie to trochę, kiedy po raz pierwszy zobaczyłem, ponieważ pozorny test nie miał żadnego potwierdzenia, co było dla mnie zapachem. Jest to fajna funkcja, ale należy omówić w zespole, czy ten atrybut powinien zostać wykorzystany w Assert.Throws
Marcel
14
+1 to także dobry sposób na sprawdzenie wyjątków. Jedyną rzeczą, o której należy pamiętać, jest to, że teoretycznie każdy wiersz kodu zgłaszający wyjątek InvalidOperationException z tym komunikatem przejdzie test, w tym kod w teście, który przygotowuje dane / obiekty testowe lub dowolną inną metodę, którą należy wykonać przed jeden, który chcesz przetestować, co może skutkować fałszywie dodatnim wynikiem. Oczywiście zależy to od tego, jak konkretna jest wiadomość i od rodzaju testowanego wyjątku. Dzięki Assert.Throwmożesz kierować na dokładnie tę linię, którą jesteś zainteresowany.
Nope
21
Atrybut ExpectedException jest przestarzały w NUnit 3: github.com/nunit/docs/wiki/Breaking-Changes
Frank
13
Assert.That(myTestDelegate, Throws.ArgumentException
    .With.Property("Message").EqualTo("your argument is invalid."));
Jordan Morris
źródło
2
Wraz z wprowadzeniem operatora nameof zredagowałbym tę doskonałą odpowiedź na:Assert.That(myTestDelegate, Throws.ArgumentException .With.Property(nameof(ArgumentException.Message)).EqualTo("your argument is invalid."));
Samuela
@Samuel Ta edycja użyłaby silnie napisanego odwołania, co jest miłe, ale z drugiej strony jest to nazwa właściwości o bardzo niskim współczynniku rezygnacji, a magiczny ciąg poprawia płynność. Podejrzewam, że to kwestia gustu
Jordan Morris,
1
Całkowicie się z tobą zgadzam Exception.Message. Nadal zalecałbym przynajmniej dodanie tej alternatywy, ponieważ With.Propertymożna ją wykorzystać również na innych obiektach, które w tym przypadku poprawiłyby stabilność kodu.
Samuel
5

To jest stare, ale istotne pytanie z nieaktualnymi odpowiedziami, więc dodaję bieżące rozwiązanie:

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

To działa z using Microsoft.VisualStudio.TestTools.UnitTesting;

Tvde1
źródło
Byłem naprawdę zaskoczony, że nie było zwięzłego sposobu na stwierdzenie, że metoda zgłasza wyjątek jak w JUnit. O ile nie są to implikacje, o których nie wiem, jest to prawdopodobnie najbardziej aktualna odpowiedź.
NetherGranite
3

Aby rozwinąć odpowiedź na trwałe i zapewnić większą funkcjonalność NUnit, możesz to zrobić:

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

Przykłady:

// 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"));
fre0n
źródło
2

Rozumiem, że od dawna nie było tego problemu, ale ostatnio natknąłem się na to samo i zasugerowałem tę funkcję dla MSTest:

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

stosowanie:

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

Więcej tutaj: http://phejndorf.wordpress.com/2011/02/21/assert-that-a-particular-exception-has-occured/

trwały
źródło
2

Ponieważ przeszkadza mi gadatliwość niektórych nowych wzorców NUnit, używam czegoś takiego do tworzenia kodu, który jest dla mnie czystszy:

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

Użycie jest wtedy:

AssertBusinessRuleException(() => user.MakeUserActive(), "Actual exception message");
Brutalny
źródło
1
Co to jest TestDelegate?
reggaeguitar
1
Pozwala przekazać kod do wykonania jako parametr. To klasa w środowisku NUnit (v3.2.0.0).
Savage