web-dev-qa-db-fra.com

Comment utiliser Assert pour vérifier qu'une exception a été levée?

Comment utiliser Assert (ou une autre classe de test?) Pour vérifier qu'une exception a été levée?

715
Alex

Pour "Visual Studio Team Test", il apparaît que vous appliquez l'attribut ExpectedException à la méthode du test.

Exemple de la documentation ici: Procédure pas à pas de tests unitaires avec Visual Studio Team Test

[TestMethod]
[ExpectedException(typeof(ArgumentException),
    "A userId of null was inappropriately allowed.")]
public void NullUserIdInConstructor()
{
   LogonInfo logonInfo = new LogonInfo(null, "P@ss0Word");
}
854
Kevin Pullin

Habituellement, votre framework de test aura une réponse à cela. Mais si ce n'est pas assez flexible, vous pouvez toujours le faire:

try {
    somethingThatShouldThrowAnException();
    Assert.Fail(); // If it gets to this line, no exception was thrown
} catch (GoodException) { }

Comme @Jonas le souligne, cela ne marche PAS pour attraper une base. Exception:

try {
    somethingThatShouldThrowAnException();
    Assert.Fail(); // raises AssertionException
} catch (Exception) {
    // Catches the assertion exception, and the test passes
}

Si vous devez absolument intercepter Exception, vous devez relancer Assert.Fail (). Mais en réalité, c’est un signe que vous ne devriez pas écrire cela à la main; vérifiez votre infrastructure de test pour les options, ou voyez si vous pouvez générer une exception plus significative à tester.

catch (AssertionException) { throw; }

Vous devriez pouvoir adapter cette approche à tout ce que vous voulez, y compris en spécifiant les types d'exceptions à intercepter. Si vous attendez seulement certains types, terminez les blocs catch avec:

} catch (GoodException) {
} catch (Exception) {
    // not the right kind of exception
    Assert.Fail();
}
230
ojrac

Ma méthode préférée pour l'implémenter est d'écrire une méthode appelée Throws et de l'utiliser comme n'importe quelle autre méthode Assert. Malheureusement, .NET ne vous permet pas d'écrire une méthode d'extension statique, vous ne pouvez donc pas utiliser cette méthode comme si elle appartenait réellement à la classe d'assertion construite; créez simplement un autre appelé MyAssert ou quelque chose de similaire. La classe ressemble à ceci:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace YourProject.Tests
{
    public static class MyAssert
    {
        public static void Throws<T>( Action func ) where T : Exception
        {
            var exceptionThrown = false;
            try
            {
                func.Invoke();
            }
            catch ( T )
            {
                exceptionThrown = true;
            }

            if ( !exceptionThrown )
            {
                throw new AssertFailedException(
                    String.Format("An exception of type {0} was expected, but not thrown", typeof(T))
                    );
            }
        }
    }
}

Cela signifie que votre test unitaire ressemble à ceci:

[TestMethod()]
public void ExceptionTest()
{
    String testStr = null;
    MyAssert.Throws<NullReferenceException>(() => testStr.ToUpper());
}

Ce qui ressemble et se comporte beaucoup plus comme le reste de la syntaxe de votre test unitaire.

103
Richiban

Si vous utilisez MSTest, qui à l'origine n'avait pas d'attribut ExpectedException, vous pouvez le faire

try 
{
    SomeExceptionThrowingMethod()
    Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
    Assert.IsTrue(ex is SpecificExceptionType);
}
59
Jon Limjap

si vous utilisez NUNIT, vous pouvez faire quelque chose comme ceci:

Assert.Throws<ExpectedException>(() => methodToTest());


Il est également possible de stocker l'exception levée afin de la valider davantage:

ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);

Voir: http://nunit.org/docs/2.5/exceptionAsserts.html

44
damir

Méfiez-vous de l'utilisation de ExpectedException, car cela peut entraîner plusieurs pièges, comme illustré ici:

http://geekswithblogs.net/sdorman/archive/2009/01/17/unit-testing-and-expected-exceptions.aspx

Et ici:

http://xunit.github.io/docs/comparisons.html

Si vous avez besoin de tester les exceptions, les méthodes sont moins mal vues. Vous pouvez utiliser la méthode try {act/fail} catch {assert}, qui peut être utile pour les frameworks ne prenant pas en charge directement les tests d'exception autres que ExpectedException. 

Une meilleure alternative consiste à utiliser xUnit.NET, un cadre de test unitaire très moderne, évolutif et évolutif qui a tiré des leçons de toutes les autres erreurs et a été amélioré. Assert.Throws est une de ces améliorations, qui fournit une syntaxe bien meilleure pour l’affirmation des exceptions. 

Vous pouvez trouver xUnit.NET sur github: http://xunit.github.io/

35
jrista

Dans un projet sur lequel je travaille, nous avons une autre solution.

Tout d’abord, je n’aime pas l’ExpectedExceptionAttribute, car il prend en considération l’appel de méthode qui a provoqué l’exception.

Je fais cela avec un helpermethod à la place.

Test

[TestMethod]
public void AccountRepository_ThrowsExceptionIfFileisCorrupt()
{
     var file = File.Create("Accounts.bin");
     file.WriteByte(1);
     file.Close();

     IAccountRepository repo = new FileAccountRepository();
     TestHelpers.AssertThrows<SerializationException>(()=>repo.GetAll());            
}

HelperMethod

public static TException AssertThrows<TException>(Action action) where TException : Exception
    {
        try
        {
            action();
        }
        catch (TException ex)
        {
            return ex;
        }
        Assert.Fail("Expected exception was not thrown");

        return null;
    }

Neat, n'est-ce pas;)

24
Glenn

C'est un attribut de la méthode de test ... vous n'utilisez pas Assert. Ressemble à ça:

[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()
15
bytebender

Vous pouvez télécharger un paquet depuis Nuget en utilisant: PM> Install-Package MSTestExtensions qui ajoute la syntaxe Assert.Throws () dans le style de nUnit/xUnit à MsTest.

Instructions de haut niveau: téléchargez l'assembly et héritez de BaseTest et vous pouvez utiliser la syntaxe Assert.Throws ().

La méthode principale pour l'implémentation de Throws se présente comme suit:

public static void Throws<T>(Action task, string expectedMessage, ExceptionMessageCompareOptions options) where T : Exception
{
    try
    {
        task();
    }
    catch (Exception ex)
    {
        AssertExceptionType<T>(ex);
        AssertExceptionMessage(ex, expectedMessage, options);
        return;
    }

    if (typeof(T).Equals(new Exception().GetType()))
    {
        Assert.Fail("Expected exception but no exception was thrown.");
    }
    else
    {
        Assert.Fail(string.Format("Expected exception of type {0} but no exception was thrown.", typeof(T)));
    }
}

Divulgation: J'ai assemblé ce paquet.

Plus d'infos: http://www.bradoncode.com/blog/2012/01/asserting-exceptions-in-mstest-with.html

13

MSTest (v2) a maintenant une fonction Assert.ThrowsException qui peut être utilisée comme ceci:

Assert.ThrowsException<System.FormatException>(() =>
            {
                Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
            }); 

Vous pouvez l'installer avec le nuget: Install-Package MSTest.TestFramework

12
Martin Beeby

Vous pouvez y parvenir avec une simple ligne.

Si votre opération foo.bar() est asynchrone:

await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());

Si foo.bar() n'est pas asynchrone

Assert.ThrowsException<Exception>(() => foo.bar());
5
Cfrim

Je ne recommande pas d'utiliser l'attribut ExpectedException (car il est trop contraignant et sujet aux erreurs) ou d'écrire un bloc try/catch dans chaque test (car il est trop compliqué et sujet aux erreurs). Utilisez une méthode d’assertion bien conçue - fournie par votre framework de test ou écrivez la vôtre. Voici ce que j'ai écrit et utilisé. 

public static class ExceptionAssert
{
    private static T GetException<T>(Action action, string message="") where T : Exception
    {
        try
        {
            action();
        }
        catch (T exception)
        {
            return exception;
        }
        throw new AssertFailedException("Expected exception " + typeof(T).FullName + ", but none was propagated.  " + message);
    }

    public static void Propagates<T>(Action action) where T : Exception
    {
        Propagates<T>(action, "");
    }

    public static void Propagates<T>(Action action, string message) where T : Exception
    {
        GetException<T>(action, message);
    }

    public static void Propagates<T>(Action action, Action<T> validation) where T : Exception
    {
        Propagates(action, validation, "");
    }

    public static void Propagates<T>(Action action, Action<T> validation, string message) where T : Exception
    {
        validation(GetException<T>(action, message));
    }
}

Exemple d'utilisation:

    [TestMethod]
    public void Run_PropagatesWin32Exception_ForInvalidExeFile()
    {
        (test setup that might propagate Win32Exception)
        ExceptionAssert.Propagates<Win32Exception>(
            () => CommandExecutionUtil.Run(Assembly.GetExecutingAssembly().Location, new string[0]));
        (more asserts or something)
    }

    [TestMethod]
    public void Run_PropagatesFileNotFoundException_ForExecutableNotFound()
    {
        (test setup that might propagate FileNotFoundException)
        ExceptionAssert.Propagates<FileNotFoundException>(
            () => CommandExecutionUtil.Run("NotThere.exe", new string[0]),
            e => StringAssert.Contains(e.Message, "NotThere.exe"));
        (more asserts or something)
    }

REMARQUES

Renvoyer l'exception au lieu de prendre en charge un rappel de validation est une idée raisonnable, sauf que cela rend la syntaxe d'appel de cette assertion très différente de celle des autres assertions que j'utilise.

Contrairement à d'autres, j'utilise «propage» au lieu de «jette», car nous ne pouvons que vérifier si une exception se propage à partir d'un appel. Nous ne pouvons pas tester directement qu'une exception est levée. Mais je suppose que vous pouvez imaginer jette pour signifier: jeté et non attrapé.

PENSÉE FINALE

Avant de passer à ce type d'approche, j'ai envisagé d'utiliser l'attribut ExpectedException lorsqu'un test vérifiait uniquement le type d'exception et d'utiliser un bloc try/catch si davantage de validation était requise. Mais, non seulement je devrais penser à la technique à utiliser pour chaque test, mais changer le code d’une technique à l’autre à mesure que les besoins évoluaient n’était pas un effort trivial. L'utilisation d'une approche cohérente permet d'économiser l'effort mental. 

En résumé, cette approche est sportive: facilité d’utilisation, souplesse et robustesse (il est difficile de mal faire les choses).

5
steve

L'assistant fourni par @Richiban ci-dessus fonctionne très bien, sauf qu'il ne gère pas la situation dans laquelle une exception est générée, mais pas le type attendu. Les adresses suivantes qui:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace YourProject.Tests
{
    public static class MyAssert
    {
        /// <summary>
        /// Helper for Asserting that a function throws an exception of a particular type.
        /// </summary>
        public static void Throws<T>( Action func ) where T : Exception
        {
            Exception exceptionOther = null;
            var exceptionThrown = false;
            try
            {
                func.Invoke();
            }
            catch ( T )
            {
                exceptionThrown = true;
            }
            catch (Exception e) {
                exceptionOther = e;
            }

            if ( !exceptionThrown )
            {
                if (exceptionOther != null) {
                    throw new AssertFailedException(
                        String.Format("An exception of type {0} was expected, but not thrown. Instead, an exception of type {1} was thrown.", typeof(T), exceptionOther.GetType()),
                        exceptionOther
                        );
                }

                throw new AssertFailedException(
                    String.Format("An exception of type {0} was expected, but no exception was thrown.", typeof(T))
                    );
            }
        }
    }
}
4
Martin Connell

Au lieu de cela, vous pouvez essayer de tester des exceptions. En fait, des exceptions sont générées avec les deux lignes suivantes de votre test.

var testDelegate = () => MyService.Method(params);
Assert.Throws<Exception>(testDelegate);
3
Matias

Dans les tests unitaires intégrés de VS, si vous souhaitez simplement vérifier que "toute exception" est générée, mais que vous ne connaissez pas le type, vous pouvez utiliser un catch all:

[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
    //...
}
3
TTT

Eh bien, je vais résumer ce que tout le monde ici a dit auparavant. En tout cas, voici le code que j'ai construit en fonction des bonnes réponses :).

/// <summary>
/// Checks to make sure that the input delegate throws a exception of type TException.
/// </summary>
/// <typeparam name="TException">The type of exception expected.</typeparam>
/// <param name="methodToExecute">The method to execute to generate the exception.</param>
public static void AssertRaises<TException>(Action methodToExecute) where TException : System.Exception
{
    try
    {
        methodToExecute();
    }
    catch (TException) {
        return;
    }  
    catch (System.Exception ex)
    {
        Assert.Fail("Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
    }
    Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");  
}
3
roeiba

Puisque vous mentionnez utiliser d'autres classes de test, une meilleure option que l'attribut ExpectedException consiste à utiliser Shoudly 's Should.Throw .

Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });

Disons que nous avons l'obligation que le client ait une adresse pour créer un order. Si ce n'est pas le cas, la méthode CreateOrderForCustomer devrait donner ArgumentException. Ensuite, nous pourrions écrire:

[TestMethod]
public void NullUserIdInConstructor()
{
  var customer = new Customer(name := "Justin", address := null};

  Should.Throw<ArgumentException>(() => {
    var order = CreateOrderForCustomer(customer) });
}

C'est mieux que d'utiliser un attribut ExpectedException car nous précisons ce qui devrait générer l'erreur. Cela clarifie les exigences de nos tests et facilite également le diagnostic en cas d'échec du test.

Notez qu'il existe également un Should.ThrowAsync pour le test de méthode asynchrone.

3
Justin J Stark

Si vous utilisez NUnit , essayez ceci:

Assert.That(() =>
        {
            Your_Method_To_Test();
        }, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
2
Amir Chatrbahr

Cela dépendra de quel framework de test utilisez-vous?

Dans MbUnit, par exemple, vous pouvez spécifier l'exception attendue avec un attribut pour vous assurer que vous obtenez l'exception que vous attendez vraiment.

[ExpectedException(typeof(ArgumentException))]
2
Jay

Consultez nUnit Docs pour des exemples sur: 

[ExpectedException( typeof( ArgumentException ) )]
2
Jon Masters

Même si cette question est ancienne, je voudrais ajouter une nouvelle pensée à la discussion. J'ai étendu le modèle Arrange, Act, Assert à attendre, Arrange, Act, Assert. Vous pouvez créer un pointeur d'exception attendu, puis affirmer qu'il a été affecté. Cela semble plus propre que de faire vos assertions dans un bloc catch, laissant votre section Act principalement pour une ligne de code uniquement appeler la méthode à tester. De plus, vous n'avez pas besoin de Assert.Fail(); ou return à partir de plusieurs points du code. Toute autre exception générée entraînera l'échec du test, car elle ne sera pas interceptée et si une exception du type attendu est générée, mais que ce n'est pas celui que vous attendiez, Affirmation contre le message ou d'autres propriétés de l'aide d'exception vous assurer que votre test ne passera pas par inadvertance.

[TestMethod]
public void Bar_InvalidDependency_ThrowsInvalidOperationException()
{
    // Expectations
    InvalidOperationException expectedException = null;
    string expectedExceptionMessage = "Bar did something invalid.";

    // Arrange
    IDependency dependency = DependencyMocks.Create();
    Foo foo = new Foo(dependency);

    // Act
    try
    {
        foo.Bar();
    }
    catch (InvalidOperationException ex)
    {
        expectedException = ex;
    }

    // Assert
    Assert.IsNotNull(expectedException);
    Assert.AreEqual(expectedExceptionMessage, expectedException.Message);
}
1
Adam Venezia

Il existe une bibliothèque géniale appelée NFluent qui accélère et facilite la rédaction de vos assertions.

Il est assez simple d'écrire une assertion pour lancer une exception:

    [Test]
    public void given_when_then()
    {
        Check.ThatCode(() => MethodToTest())
            .Throws<Exception>()
            .WithMessage("Process has been failed");
    }
0
mirind4