Wie verwende ich Assert.Throws, um den Typ der Ausnahme zu bestätigen?

246

Wie kann ich Assert.Throwsden Typ der Ausnahme und den tatsächlichen Wortlaut der Nachricht bestätigen?

Etwas wie das:

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

Die Methode, die ich teste, wirft mehrere Nachrichten desselben Typs mit unterschiedlichen Nachrichten aus, und ich brauche eine Möglichkeit, um zu testen, ob die richtige Nachricht je nach Kontext ausgelöst wird.

Epitka
quelle

Antworten:

443

Assert.Throws Gibt die ausgelöste Ausnahme zurück, mit der Sie die Ausnahme bestätigen können.

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

Wenn also keine Ausnahme ausgelöst wird oder eine Ausnahme des falschen Typs ausgelöst wird, schlägt die erste Assert.ThrowsZusicherung fehl. Wenn jedoch eine Ausnahme des richtigen Typs ausgelöst wird, können Sie jetzt die tatsächliche Ausnahme bestätigen, die Sie in der Variablen gespeichert haben.

Mit diesem Muster können Sie andere Dinge als die Ausnahmemeldung bestätigen, z. B. im Fall von ArgumentExceptionund Ableitungen, und Sie können behaupten, dass der Parametername korrekt ist:

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

Sie können auch die fließende API verwenden, um diese Asserts auszuführen:

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

oder alternativ

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

Ein kleiner Tipp beim Aktivieren von Ausnahmemeldungen besteht darin, die Testmethode mit zu dekorieren SetCultureAttribute, um sicherzustellen, dass die ausgelöste Nachricht die erwartete Kultur verwendet. Dies kommt ins Spiel, wenn Sie Ihre Ausnahmemeldungen als Ressourcen speichern, um die Lokalisierung zu ermöglichen.

Patrik Hägne
quelle
Dies war sehr hilfreich für mich - ich wollte eine Möglichkeit, den Fehler anzuzeigen. Ich habe nicht einmal gelesen, ob ein Wert von der Assert.Throws-Methode zurückgegeben wurde. Vielen Dank
Haroon
6
+1 Vielen Dank, dass Sie die Fluent-API gezeigt haben. Aus irgendeinem Grund hatte ich Probleme, die Verwendung nur anhand der NUnit-Dokumente zu verstehen.
Aolszowka
Wenn Sie die Nachricht bestätigen möchten, können Sie anstelle der "Eigenschaft" auch direkt die Message-Eigenschaft verwenden.
Marcel
25

Sie können jetzt die ExpectedExceptionAttribute verwenden, z

[Test]
[ExpectedException(typeof(InvalidOperationException), 
 ExpectedMessage="You can't do that!"]
public void MethodA_WithNull_ThrowsInvalidOperationException()
{
    MethodA(null);
}
Jackson Pope
quelle
2
Dies störte mich ein wenig, als ich es zum ersten Mal sah, weil der Test offensichtlich keine Behauptung hatte, was für mich ein Geruch war. Dies ist eine nette Funktion, aber man sollte im Team diskutieren, ob dieses Attribut über den Assert verwendet werden soll. Throws
Marcel
14
+1 auch eine gute Möglichkeit, um auf Ausnahmen zu testen. Das einzige, was Sie dabei beachten sollten, ist, dass theoretisch jede Codezeile, die eine InvalidOperationException mit dieser Nachricht auslöst, den Test besteht, einschließlich Code in Ihrem Test, der die Testdaten / -objekte oder eine andere Methode vorbereitet, die Sie möglicherweise vor dem ausführen müssen eine, die Sie testen möchten, was möglicherweise zu einem falsch positiven Ergebnis führt. Das hängt natürlich davon ab, wie spezifisch die Nachricht ist und welche Art von Ausnahme Sie testen. Mit können Assert.ThrowSie genau die Linie anvisieren, an der Sie interessiert sind.
Nein,
21
Das ExpectedException-Attribut ist in NUnit 3 veraltet: github.com/nunit/docs/wiki/Breaking-Changes
Frank Sebastià
13
Assert.That(myTestDelegate, Throws.ArgumentException
    .With.Property("Message").EqualTo("your argument is invalid."));
Jordan Morris
quelle
2
Mit der Einführung des Namens des Operators würde ich diese ausgezeichnete Antwort bearbeiten an:Assert.That(myTestDelegate, Throws.ArgumentException .With.Property(nameof(ArgumentException.Message)).EqualTo("your argument is invalid."));
Samuel
@Samuel Diese Bearbeitung würde eine stark typisierte Referenz verwenden, was nett ist, aber andererseits ist es ein extrem niedriger Eigenschaftsname und die magische Zeichenfolge verbessert die Sprachkompetenz. Eine Frage des Geschmacks, nehme ich an
Jordan Morris
1
Da stimme ich Ihnen voll und ganz zu Exception.Message. Ich würde immer noch empfehlen, diese Alternative zumindest hinzuzufügen, da With.Propertysie auch für andere Objekte verwendet werden kann, was in diesem Fall die Stabilität des Codes verbessern würde.
Samuel
5

Dies ist eine alte, aber relevante Frage mit veralteten Antworten, daher füge ich die aktuelle Lösung hinzu:

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

Das funktioniert mit using Microsoft.VisualStudio.TestTools.UnitTesting;

Tvde1
quelle
Ich war wirklich überrascht, dass es keine präzise Möglichkeit gab, zu behaupten, dass eine Methode eine Ausnahme wie in JUnit auslöst. Sofern mir keine Implikationen bekannt sind, ist dies derzeit wahrscheinlich die relevanteste Antwort.
NetherGranite
3

Um die Antwort von persistent zu erweitern und mehr Funktionen von NUnit bereitzustellen, können Sie Folgendes tun:

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

Beispiele:

// 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
quelle
2

Es ist lange her, dass dieses Problem angesprochen wurde, aber ich bin kürzlich auf dasselbe gestoßen und schlage diese Funktion für MSTest vor:

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

Verwendung:

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

Mehr hier: http://phejndorf.wordpress.com/2011/02/21/assert-that-a-particular-exception-has-occured/

hartnäckig
quelle
2

Da mich die Ausführlichkeit einiger der neuen NUnit-Muster stört, verwende ich so etwas, um Code zu erstellen, der für mich persönlich sauberer ist:

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

Die Verwendung ist dann:

AssertBusinessRuleException(() => user.MakeUserActive(), "Actual exception message");
Wild
quelle
1
Was ist TestDelegate?
Reggaeguitar
1
Hier können Sie den Code übergeben, der als Parameter ausgeführt werden soll. Es ist eine Klasse im NUnit-Framework (v3.2.0.0).
Savage