Comment utiliser Assert.Throws pour affirmer le type de l'exception?

247

Comment puis-je utiliser Assert.Throwspour affirmer le type de l'exception et le libellé du message réel.

Quelque chose comme ça:

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

La méthode que je teste lance plusieurs messages du même type, avec des messages différents, et j'ai besoin d'un moyen de tester que le bon message est lancé en fonction du contexte.

epitka
la source

Réponses:

444

Assert.Throws renvoie l'exception levée qui vous permet d'affirmer l'exception.

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

Donc, si aucune exception n'est levée ou si une exception de type incorrect est levée, la première Assert.Throwsassertion échouera. Cependant, si une exception du type correct est levée, vous pouvez maintenant affirmer l'exception réelle que vous avez enregistrée dans la variable.

En utilisant ce modèle, vous pouvez affirmer autre chose que le message d'exception, par exemple dans le cas de ArgumentExceptionet dérivés, vous pouvez affirmer que le nom du paramètre est correct:

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

Vous pouvez également utiliser l'API couramment pour effectuer ces assertions:

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

Ou bien

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

Une petite astuce lors de l'affirmation sur les messages d'exception consiste à décorer la méthode de test avec SetCultureAttributepour vous assurer que le message renvoyé utilise la culture attendue. Cela entre en jeu si vous stockez vos messages d'exception en tant que ressources pour permettre la localisation.

Patrik Hägne
la source
Cela m'a été très utile - je voulais un moyen d'afficher l'erreur, je n'ai même pas lu si une valeur était retournée par la méthode Assert.Throws. Merci
Haroon
6
+1 Merci d'avoir montré l'API Fluent, pour une raison quelconque, j'avais du mal à comprendre comment l'utiliser uniquement à partir des documents NUnit uniquement.
aolszowka
Lorsque vous souhaitez affirmer le message, vous pouvez également utiliser directement la propriété Message au lieu de la «propriété».
Marcel
25

Vous pouvez maintenant utiliser les ExpectedExceptionattributs, par exemple

[Test]
[ExpectedException(typeof(InvalidOperationException), 
 ExpectedMessage="You can't do that!"]
public void MethodA_WithNull_ThrowsInvalidOperationException()
{
    MethodA(null);
}
Jackson Pope
la source
2
Cela m'a un peu dérangé lors de la première vue, car le test n'avait apparemment aucune affirmation, ce qui était une odeur pour moi. C'est une fonctionnalité intéressante, mais il convient de discuter dans l'équipe de la question de savoir si cet attribut devrait être utilisé au cours de l'assertion.Throws
Marcel
14
+1 également un bon moyen de tester les exceptions. La seule chose à garder à l'esprit à ce sujet est que, théoriquement, toute ligne de code lançant une exception InvalidOperationException avec ce message passera le test, y compris le code de votre test qui prépare les données / objets de test ou toute autre méthode que vous pourriez avoir besoin d'exécuter avant le celui que vous êtes intéressé à tester, ce qui pourrait entraîner un faux positif. Bien sûr, cela dépend de la spécificité du message et du type d'exception que vous testez. Avec, Assert.Throwvous pouvez cibler la ligne exacte qui vous intéresse.
Nope
21
L'attribut ExpectedException est déconseillé dans 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."));
Jordan Morris
la source
2
Avec l'introduction du nom de l'opérateur, je modifierais cette excellente réponse pour:Assert.That(myTestDelegate, Throws.ArgumentException .With.Property(nameof(ArgumentException.Message)).EqualTo("your argument is invalid."));
Samuel
@Samuel Cette modification utiliserait une référence fortement typée, ce qui est bien, mais d'un autre côté, c'est un nom de propriété extrêmement faible et la chaîne magique améliore la fluidité. Une question de goût je suppose
Jordan Morris
1
Je suis entièrement d'accord avec vous concernant Exception.Message. Je recommanderais toujours d'ajouter au moins cette alternative car elle With.Propertypeut également être utilisée sur d'autres objets, ce qui améliorerait la stabilité du code.
Samuel
5

Il s'agit d'une question ancienne mais pertinente avec des réponses obsolètes, j'ajoute donc la solution actuelle:

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

Cela fonctionne avec using Microsoft.VisualStudio.TestTools.UnitTesting;

Tvde1
la source
J'ai été vraiment surpris qu'il n'y ait aucun moyen concis d'affirmer qu'une méthode lève une exception comme dans JUnit. Sauf s'il y a des implications que je ne connais pas, c'est probablement la réponse la plus pertinente actuellement.
NetherGranite
3

Pour développer la réponse persistante et fournir davantage de fonctionnalités de NUnit, vous pouvez procéder comme suit:

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

Exemples:

// 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
la source
2

Il y a longtemps que ce problème a été soulevé, je m'en rends compte, mais j'ai récemment rencontré la même chose et je suggère cette fonction pour MSTest:

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

usage:

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

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

persistant
la source
2

Comme je suis dérangé par la verbosité de certains des nouveaux modèles NUnit, j'utilise quelque chose comme ça pour créer du code qui est plus propre pour moi personnellement:

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'utilisation est alors:

AssertBusinessRuleException(() => user.MakeUserActive(), "Actual exception message");
Sauvage
la source
1
Qu'est-ce que TestDelegate?
reggaeguitar
1
Il vous permet de passer le code à exécuter en tant que paramètre. C'est une classe dans le framework NUnit (v3.2.0.0).
Savage