Come posso usare Assert.Throws per affermare il tipo di eccezione?


247

Come posso utilizzare Assert.Throwsper affermare il tipo di eccezione e la formulazione effettiva del messaggio.

Qualcosa come questo:

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

Il metodo che sto testando genera più messaggi dello stesso tipo, con messaggi diversi, e ho bisogno di un modo per testare che il messaggio corretto venga lanciato a seconda del contesto.

Risposte:


444

Assert.Throws restituisce l'eccezione generata che consente di affermare l'eccezione.

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

Pertanto, se non viene generata alcuna eccezione o viene generata un'eccezione di tipo errato, la prima Assert.Throwsasserzione fallirà. Tuttavia, se viene generata un'eccezione del tipo corretto, ora puoi affermare l'eccezione effettiva che hai salvato nella variabile.

Utilizzando questo modello è possibile affermare altre cose oltre al messaggio di eccezione, ad esempio nel caso di ArgumentExceptione derivati, è possibile affermare che il nome del parametro è corretto:

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

Puoi anche usare l'API fluente per fare questi assert:

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

o in alternativa

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

Un piccolo suggerimento quando si asserisce sui messaggi di eccezione è quello di decorare il metodo di test con SetCultureAttributeper assicurarsi che il messaggio generato stia utilizzando la cultura prevista. Questo entra in gioco se si memorizzano i messaggi di eccezione come risorse per consentire la localizzazione.


Questo è stato davvero utile per me: volevo un modo per visualizzare l'errore, non ho nemmeno letto se un valore è stato restituito dal metodo Assert.Throws. Grazie
Haroon,

6
+1 Grazie per aver mostrato l'API Fluent, per qualche motivo ho avuto problemi a capire come usarlo solo dai documenti NUnit.
aolszowka,

Quando si desidera affermare il messaggio, è anche possibile utilizzare direttamente la proprietà Message anziché la "Proprietà".
Marcel,

25

Ora puoi usare gli ExpectedExceptionattributi, ad es

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

2
Questo mi ha disturbato un po 'quando è stato visto per la prima volta, perché il test apparentemente non aveva alcuna affermazione, il che era un odore per me. Questa è una bella caratteristica, ma si dovrebbe discutere in squadra se questo attributo dovrebbe essere usato durante l'Assert.Throws
Marcel

14
+1 anche un bel modo per verificare le eccezioni. L'unica cosa da tenere a mente è che teoricamente qualsiasi riga di codice che lancia un InvalidOperationException con quel messaggio supererà il test, incluso il codice nel test che prepara i dati / oggetti del test o qualsiasi altro metodo che potrebbe essere necessario eseguire prima del uno che sei interessato a testare, eventualmente con conseguente falso positivo. Ovviamente ciò dipende dalla specificità del messaggio e dal tipo di eccezione che si sta testando. Con Assert.Throwte puoi scegliere come target la linea esatta a cui sei interessato.
No

21
L'attributo ExpectedException è obsoleto in NUnit 3: github.com/nunit/docs/wiki/Breaking-Changes
Frank Sebastià

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

2
Con l'introduzione del nome dell'operatore modificherei questa eccellente risposta in:Assert.That(myTestDelegate, Throws.ArgumentException .With.Property(nameof(ArgumentException.Message)).EqualTo("your argument is invalid."));
Samuel,

@Samuel Quella modifica userebbe un riferimento fortemente tipizzato che è bello, ma d'altra parte, è un nome di proprietà estremamente basso e la stringa magica migliora la fluidità. Immagino che sia una questione di gusti
Jordan Morris,

1
Sono completamente d'accordo con te per quanto riguarda Exception.Message. Consiglio comunque di aggiungere almeno questa alternativa perché With.Propertypuò essere utilizzata anche su altri oggetti, che in questo caso migliorerebbero la stabilità del codice.
Samuel

5

Questa è una domanda vecchia ma pertinente con risposte obsolete, quindi sto aggiungendo la soluzione attuale:

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

Questo funziona con using Microsoft.VisualStudio.TestTools.UnitTesting;


Sono stato davvero sorpreso che non esistesse un modo conciso per affermare che un metodo genera un'eccezione come in JUnit. A meno che non ci siano implicazioni di cui non sono a conoscenza, questa è probabilmente la risposta più rilevante al momento.
NetherGranite,

3

Per espandere la risposta persistente e fornire più funzionalità di NUnit, è possibile effettuare ciò:

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

Esempi:

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

È passato molto tempo da quando è stato sollevato questo problema, mi rendo conto, ma di recente mi sono imbattuto nella stessa cosa e suggerisco questa funzione per MSTest:

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

utilizzo:

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

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


2

Dato che sono disturbato dalla verbosità di alcuni dei nuovi modelli NUnit, uso qualcosa del genere per creare codice che sia più pulito per me personalmente:

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

L'utilizzo è quindi:

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

1
Che cos'è TestDelegate?
reggaeguitar,

1
Ti permette di passare il codice da eseguire come parametro. È una classe nel framework NUnit (v3.2.0.0).
Savage
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.