java - content - junit test expected to throw exception




Comment affirmez-vous qu'une certaine exception est levée dans JUnit 4 tests? (20)

Solution Java 8

Si vous souhaitez une solution qui:

  • Utilise Java 8 lambdas
  • Ne dépend pas de la magie JUnit
  • Vous permet de vérifier plusieurs exceptions dans une seule méthode de test
  • Vérifie qu'une exception est levée par un ensemble spécifique de lignes dans votre méthode de test au lieu d'une ligne inconnue dans la méthode de test entière
  • Donne l'objet d'exception réel qui a été lancé afin que vous puissiez l'examiner plus en détail

Voici une fonction d'utilité que j'ai écrite:

public final <T extends Throwable> T expectException( Class<T> exceptionClass, Runnable runnable )
{
    try
    {
        runnable.run();
    }
    catch( Throwable throwable )
    {
        if( throwable instanceof AssertionError && throwable.getCause() != null )
            throwable = throwable.getCause(); //allows "assert x != null : new IllegalArgumentException();"
        assert exceptionClass.isInstance( throwable ) : throwable; //exception of the wrong kind was thrown.
        assert throwable.getClass() == exceptionClass : throwable; //exception thrown was a subclass, but not the exact class, expected.
        @SuppressWarnings( "unchecked" )
        T result = (T)throwable;
        return result;
    }
    assert false; //expected exception was not thrown.
    return null; //to keep the compiler happy.
}

( tiré de mon blog )

Utilisez-le comme suit:

@Test
public void testThrows()
{
    RuntimeException e = expectException( RuntimeException.class, () -> 
        {
            throw new RuntimeException( "fail!" );
        } );
    assert e.getMessage().equals( "fail!" );
}

Comment puis-je utiliser idiomatiquement JUnit4 pour tester qu'un code lève une exception?

Alors que je peux certainement faire quelque chose comme ça:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  boolean thrown = false;

  try {
    foo.doStuff();
  } catch (IndexOutOfBoundsException e) {
    thrown = true;
  }

  assertTrue(thrown);
}

Je me souviens qu'il y a une annotation ou un Assert.xyz ou quelque chose qui est beaucoup moins kludgy et beaucoup plus dans l'esprit de JUnit pour ce genre de situations.


JUnit 5 Solution

@Test
void testFooThrowsIndexOutOfBoundsException() {    
  Throwable exception = expectThrows( IndexOutOfBoundsException.class, foo::doStuff );

  assertEquals( "some message", exception.getMessage() );
}

Plus d'infos sur JUnit 5 sur improved


À mon humble avis, la meilleure façon de vérifier les exceptions dans JUnit est le modèle try / catch / fail / assert:

// this try block should be as small as possible,
// as you want to make sure you only catch exceptions from your code
try {
    sut.doThing();
    fail(); // fail if this does not throw any exception
} catch(MyException e) { // only catch the exception you expect,
                         // otherwise you may catch an exception for a dependency unexpectedly
    // a strong assertion on the message, 
    // in case the exception comes from anywhere an unexpected line of code,
    // especially important if your checking IllegalArgumentExceptions
    assertEquals("the message I get", e.getMessage()); 
}

Le assertTrue peut être un peu fort pour certaines personnes, donc assertThat(e.getMessage(), containsString("the message"); peut être préférable.


Comme il a déjà été répondu, il existe de nombreuses manières de traiter les exceptions dans JUnit. Mais avec Java 8 il y en a un autre: utiliser des expressions Lambda. Avec les expressions Lambda, nous pouvons obtenir une syntaxe comme celle-ci:

@Test
public void verifiesTypeAndMessage() {
    assertThrown(new DummyService()::someMethod)
            .isInstanceOf(RuntimeException.class)
            .hasMessage("Runtime exception occurred")
            .hasMessageStartingWith("Runtime")
            .hasMessageEndingWith("occurred")
            .hasMessageContaining("exception")
            .hasNoCause();
}

assertThrown accepte une interface fonctionnelle, dont les instances peuvent être créées avec des expressions lambda, des références de méthode ou des références de constructeurs. assertThrown acceptant cette interface attendra et sera prêt à gérer une exception.

C'est une technique relativement simple mais puissante.

Jetez un oeil à cet article de blog décrivant cette technique: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

Le code source peut être trouvé ici: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

Divulgation: Je suis l'auteur du blog et du projet.


Dans mon cas, j'ai toujours RuntimeException de DB, mais les messages diffèrent. Et l'exception doit être gérée respectivement. Voici comment je l'ai testé:

@Test
public void testThrowsExceptionWhenWrongSku() {

    // Given
    String articleSimpleSku = "999-999";
    int amountOfTransactions = 1;
    Exception exception = null;

    // When
    try {
        createNInboundTransactionsForSku(amountOfTransactions, articleSimpleSku);
    } catch (RuntimeException e) {
        exception = e;
    }

    // Then
    shouldValidateThrowsExceptionWithMessage(exception, MESSAGE_NON_EXISTENT_SKU);
}

private void shouldValidateThrowsExceptionWithMessage(final Exception e, final String message) {
    assertNotNull(e);
    assertTrue(e.getMessage().contains(message));
}

En utilisant une assertion AssertJ , qui peut être utilisée avec JUnit:

import static org.assertj.core.api.Assertions.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  Foo foo = new Foo();

  assertThatThrownBy(() -> foo.doStuff())
        .isInstanceOf(IndexOutOfBoundsException.class);
}

C'est mieux que @Test(expected=IndexOutOfBoundsException.class) car il garantit que la ligne attendue dans le test a jeté l'exception et vous permet de vérifier plus de détails sur l'exception, comme un message, plus facile:

assertThatThrownBy(() ->
       {
         throw new Exception("boom!");
       })
    .isInstanceOf(Exception.class)
    .hasMessageContaining("boom");

Instructions Maven / Gradle ici.


J'ai essayé beaucoup de méthodes ici, mais elles étaient compliquées ou ne répondaient pas à mes exigences. En fait, on peut écrire une méthode d'assistance très simplement:

public class ExceptionAssertions {
    public static void assertException(BlastContainer blastContainer ) {
        boolean caughtException = false;
        try {
            blastContainer.test();
        } catch( Exception e ) {
            caughtException = true;
        }
        if( !caughtException ) {
            throw new AssertionFailedError("exception expected to be thrown, but was not");
        }
    }
    public static interface BlastContainer {
        public void test() throws Exception;
    }
}

Utilisez-le comme ceci:

assertException(new BlastContainer() {
    @Override
    public void test() throws Exception {
        doSomethingThatShouldExceptHere();
    }
});

Dépendances nulles: pas besoin de mockito, pas besoin de powermock; et fonctionne très bien avec les classes finales.


JUnit 4 a le support pour ceci:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);
}

Référence: https://junit.org/junit4/faq.html#atests_7


Maintenant que JUnit 5 a été publié, la meilleure option consiste à utiliser Assertions.assertThrows() (voir le Guide de l'utilisateur de Junit 5 ).

Voici un exemple qui vérifie qu'une exception est levée et utilise Truth pour faire des assertions sur le message d'exception:

public class FooTest {
  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    IndexOutOfBoundsException e = assertThrows(
        IndexOutOfBoundsException.class, foo::doStuff);

    assertThat(e).hasMessageThat().contains("woops!");
  }
}

Les avantages par rapport aux approches dans les autres réponses sont:

  1. Construit en JUnit
  2. Vous obtenez un message d'exception utile si le code dans lambda ne lance pas d'exception et un stacktrace s'il lance une exception différente
  3. Concis
  4. Permet à vos tests de suivre Arrange-Act-Assert
  5. Vous pouvez indiquer précisément quel code vous attendez de l'exception
  6. Vous n'avez pas besoin d'afficher l'exception attendue dans la clause throws
  7. Vous pouvez utiliser le cadre d'assertion de votre choix pour faire des assertions sur l'exception interceptée

Une méthode similaire sera ajoutée à org.junit Assert dans JUnit 4.13.


Pour résoudre le même problème, j'ai mis en place un petit projet: http://code.google.com/p/catch-exception/

En utilisant cette petite aide vous écririez

verifyException(foo, IndexOutOfBoundsException.class).doStuff();

C'est moins verbeux que la règle ExpectedException de JUnit 4.7. En comparaison avec la solution fournie par skaffman, vous pouvez spécifier dans quelle ligne de code vous attendez l'exception. J'espère que ça aide.


Vous pouvez aussi faire ceci:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
    try {
        foo.doStuff();
        assert false;
    } catch (IndexOutOfBoundsException e) {
        assert true;
    }
}

dans junit, il existe trois façons de tester l'exception.

  • utiliser l'attribut facultatif 'expected' de Test Annotation

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
    
  • utilisez la règle ExpectedException

    public class XxxTest {
        @Rule
        public ExpectedException thrown = ExpectedException.none();
    
        @Test
        public void testFooThrowsIndexOutOfBoundsException() {
            thrown.expect(IndexOutOfBoundsException.class)
            //you can test the exception message like
            thrown.expectMessage("expected messages");
            foo.doStuff();
        }
    }
    
  • Enfin, vous pouvez également utiliser la méthode classique try / catch largement utilisée dans le cadre de junit 3

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        try {
            foo.doStuff();
            fail("expected exception was not occured.");
        } catch(IndexOutOfBoundsException e) {
            //if execution reaches here, 
            //it indicates this exception was occured.
            //so we need not handle it.
        }
    }
    
  • alors

    • la 1ère manière utilisée quand vous voulez seulement tester le type d'exception
    • la 2ème et 3ème manière utilisée quand vous voulez que le message d'exception de test continue
    • Si vous utilisez junit 3, alors le 3ème est préféré.
  • pour plus d'informations, vous pouvez lire github.com/junit-team/junit/wiki/Exception-testing pour plus de détails.


Modifier Maintenant que JUnit5 est sorti, la meilleure option serait d'utiliser Assertions.assertThrows() (voir mon autre réponse ).

Si vous n'avez pas migré vers JUnit 5, mais que vous pouvez utiliser JUnit 4.7, vous pouvez utiliser la règle ExpectedException :

public class FooTest {
  @Rule
  public final ExpectedException exception = ExpectedException.none();

  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    exception.expect(IndexOutOfBoundsException.class);
    foo.doStuff();
  }
}

C'est beaucoup mieux que @Test(expected=IndexOutOfBoundsException.class) parce que le test échouera si IndexOutOfBoundsException est levée avant foo.doStuff()

Voir cet article pour plus de détails


tl; dr

  • pré-JDK8: Je vais recommander le bon vieux bloc try - catch .

  • post-JDK8: Utilisez AssertJ ou lambda personnalisé pour affirmer un comportement exceptionnel .

la longue histoire

Il est possible d'écrire vous-même un bloc try - catch ou d'utiliser les outils JUnit ( @Test(expected = ...) ou la règle @Rule ExpectedException JUnit).

Mais ces moyens ne sont pas si élégants et ne mélangent pas bien la lisibilité avec d'autres outils.

  1. Le bloc try - catch vous devez écrire le bloc autour du comportement testé, et écrire l'assertion dans le bloc catch, cela peut être bien mais beaucoup trouvent que ce style interrompt le flux de lecture d'un test. Aussi, vous devez écrire un Assert.fail à la fin du bloc try sinon le test risque de manquer un côté des assertions; PMD , findbugs ou Sonar détecteront ces problèmes.

  2. La fonction @Test(expected = ...) est intéressante car vous pouvez écrire moins de code, et écrire ce test est censé être moins sujet aux erreurs de codage. Mais cette approche fait défaut à certains domaines.

    • Si le test doit vérifier des éléments supplémentaires sur l'exception comme la cause ou le message (les bons messages d'exception sont vraiment importants, avoir un type d'exception précis peut ne pas suffire).
    • En fonction de la façon dont le code testé est écrit, la mauvaise partie du code de test peut déclencher l'exception, entraînant un test faussement positif et je ne suis pas sûr que PMD , findbugs ou Sonar donneront astuces sur un tel code.

      @Test(expected = WantedException.class)
      public void call2_should_throw_a_WantedException__not_call1() {
          // init tested
          tested.call1(); // may throw a WantedException
      
          // call to be actually tested
          tested.call2(); // the call that is supposed to raise an exception
      }
      
  3. La règle ExpectedException est également une tentative de corriger les mises en garde précédentes, mais elle semble un peu délicate à utiliser car elle utilise un style d'attente, les utilisateurs d' EasyMock connaissent très bien ce style. Cela peut convenir à certains, mais si vous suivez les principes BDD ( Behavior Driven Development ) ou AAA ( Arrange Act Assert ), la règle ExpectedException ne rentrera pas dans ce style d'écriture. En dehors de cela, il peut souffrir de la même question que le chemin de @Test , selon l'endroit où vous placez l'attente.

    @Rule ExpectedException thrown = ExpectedException.none()
    
    @Test
    public void call2_should_throw_a_WantedException__not_call1() {
        // expectations
        thrown.expect(WantedException.class);
        thrown.expectMessage("boom");
    
        // init tested
        tested.call1(); // may throw a WantedException
    
        // call to be actually tested
        tested.call2(); // the call that is supposed to raise an exception
    }
    

    Même l'exception attendue est placée avant l'instruction de test, elle casse votre flux de lecture si les tests suivent BDD ou AAA.

    Voir aussi ce problème de comment sur JUnit de l'auteur de ExpectedException .

Donc, ces options ci-dessus ont toutes leurs charges de caveats, et clairement pas immunisé contre les erreurs de codeur.

  1. Il y a un projet dont j'ai pris conscience après avoir créé cette réponse qui semble prometteuse, c'est catch-exception .

    Comme l'indique la description du projet, il permet à un codeur d'écrire dans une ligne de code fluide en attrapant l'exception et d'offrir cette exception pour une assertion ultérieure. Et vous pouvez utiliser n'importe quelle bibliothèque d'assertion comme Hamcrest ou AssertJ .

    Un exemple rapide tiré de la page d'accueil:

    // given: an empty list
    List myList = new ArrayList();
    
    // when: we try to get the first element of the list
    when(myList).get(1);
    
    // then: we expect an IndexOutOfBoundsException
    then(caughtException())
            .isInstanceOf(IndexOutOfBoundsException.class)
            .hasMessage("Index: 1, Size: 0") 
            .hasNoCause();
    

    Comme vous pouvez le voir, le code est très simple, vous attrapez l'exception sur une ligne spécifique, then API est un alias qui utilisera les API AssertJ (similaire à l'utilisation de assertThat(ex).hasNoCause()... ). À un moment donné, le projet s'est appuyé sur FEST-Assert l'ancêtre de AssertJ . EDIT: Il semble que le projet prépare un support Java 8 Lambda.

    Actuellement cette bibliothèque a deux défauts:

    • Au moment d'écrire ces lignes, il est intéressant de noter que cette bibliothèque est basée sur Mockito 1.x car elle crée un faux de l'objet testé derrière la scène. Comme Mockito n'est toujours pas mis à jour, cette bibliothèque ne peut pas fonctionner avec les classes finales ou les méthodes finales . Et même si elle était basée sur mockito 2 dans la version actuelle, cela nécessiterait de déclarer un fabricant de simulateurs globaux ( inline-mock-maker ), ce qui ne correspond pas à ce que vous voulez, car ce simulacre a des inconvénients différents du simulacre habituel.

    • Cela nécessite encore une autre dépendance de test.

    Ces problèmes ne s'appliqueront pas une fois que la bibliothèque prendra en charge lambdas, cependant la fonctionnalité sera dupliquée par l'ensemble d'outils AssertJ.

    Si vous ne tenez pas compte de l'outil catch-exception, je vous recommanderai l'ancien bon moyen du try - catch , au moins jusqu'au JDK7. Et pour les utilisateurs de JDK 8, vous préférerez peut-être utiliser AssertJ car il offre bien plus que de simples affirmations d'exceptions.

  2. Avec le JDK8, les lambdas entrent dans la scène de test, et ils se sont révélés être un moyen intéressant d'affirmer un comportement exceptionnel. AssertJ a été mis à jour pour fournir une API fluide agréable pour affirmer un comportement exceptionnel.

    Et un exemple de test avec AssertJ :

    @Test
    public void test_exception_approach_1() {
        ...
        assertThatExceptionOfType(IOException.class)
                .isThrownBy(() -> someBadIOOperation())
                .withMessage("boom!"); 
    }
    
    @Test
    public void test_exception_approach_2() {
        ...
        assertThatThrownBy(() -> someBadIOOperation())
                .isInstanceOf(Exception.class)
                .hasMessageContaining("boom");
    }
    
    @Test
    public void test_exception_approach_3() {
        ...
        // when
        Throwable thrown = catchThrowable(() -> someBadIOOperation());
    
        // then
        assertThat(thrown).isInstanceOf(Exception.class)
                          .hasMessageContaining("boom");
    }
    
  3. Avec une réécriture presque complète de JUnit 5, les assertions ont été improved un peu, elles peuvent s'avérer intéressantes comme une façon originale d'affirmer correctement l'exception. Mais vraiment l'API d'assertion est encore un peu pauvre, il n'y a rien en dehors de assertThrows .

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }
    

    Comme vous l'avez remarqué, assertEquals toujours void , et en tant que tel ne permet pas d'assertions en chaîne comme AssertJ.

    Aussi, si vous vous souvenez du conflit de noms avec Matcher ou Assert , préparez-vous à rencontrer le même conflit avec Assertions .

J'aimerais conclure qu'aujourd'hui (2017-03-03) la facilité d'utilisation d' AssertJ , son API détectable , son rythme de développement rapide et sa dépendance aux tests de fait sont la meilleure solution avec JDK8 quel que soit le cadre de test (JUnit ou pas), les JDK antérieurs devraient plutôt compter sur catch blocs d' try , même s'ils se sentent maladroits.

Cette réponse a été copiée à partir d' une autre question qui n'a pas la même visibilité, je suis le même auteur.


I wanted to comment with my solution to this problem, which avoided needing any of the exception related JUnit code.

I used assertTrue(boolean) combined with try/catch to look for my expected exception to be thrown. Voici un exemple:

public void testConstructor() {
    boolean expectedExceptionThrown;
    try {
        // Call constructor with bad arguments
        double a = 1;
        double b = 2;
        double c = a + b; // In my example, this is an invalid option for c
        new Triangle(a, b, c);
        expectedExceptionThrown = false; // because it successfully constructed the object
    }
    catch(IllegalArgumentException e) {
        expectedExceptionThrown = true; // because I'm in this catch block
    }
    catch(Exception e) {
        expectedExceptionThrown = false; // because it threw an exception but not the one expected
    }
    assertTrue(expectedExceptionThrown);
}

Junit4 solution with Java8 is to use this function:

public Throwable assertThrows(Class<? extends Throwable> expectedException, java.util.concurrent.Callable<?> funky) {
    try {
        funky.call();
    } catch (Throwable e) {
        if (expectedException.isInstance(e)) {
            return e;
        }
        throw new AssertionError(
                String.format("Expected [%s] to be thrown, but was [%s]", expectedException, e));
    }
    throw new AssertionError(
            String.format("Expected [%s] to be thrown, but nothing was thrown.", expectedException));
}

Usage is then:

    assertThrows(ValidationException.class,
            () -> finalObject.checkSomething(null));

Note that the only limitation is to use a final object reference in lambda expression. This solution allows to continue test assertions instead of expecting thowable at method level using @Test(expected = IndexOutOfBoundsException.class) solution.


My solution using Java 8 lambdas:

public static <T extends Throwable> T assertThrows(Class<T> expected, ThrowingRunnable action) throws Throwable {
    try {
        action.run();
        Assert.fail("Did not throw expected " + expected.getSimpleName());
        return null; // never actually
    } catch (Throwable actual) {
        if (!expected.isAssignableFrom(actual.getClass())) { // runtime '!(actual instanceof expected)'
            System.err.println("Threw " + actual.getClass().getSimpleName() 
                               + ", which is not a subtype of expected " 
                               + expected.getSimpleName());
            throw actual; // throw the unexpected Throwable for maximum transparency
        } else {
            return (T) actual; // return the expected Throwable for further examination
        }
    }
}

You have to define a FunctionalInterface, because Runnable doesn't declare the required throws .

@FunctionalInterface
public interface ThrowingRunnable {
    void run() throws Throwable;
}

The method can be used as follows:

class CustomException extends Exception {
    public final String message;
    public CustomException(final String message) { this.message = message;}
}
CustomException e = assertThrows(CustomException.class, () -> {
    throw new CustomException("Lorem Ipsum");
});
assertEquals("Lorem Ipsum", e.message);

Take for example, you want to write Junit for below mentioned code fragment

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

    throw new ArrayIndexOutOfBoundsException("Array is out of bound");
}

The above code is to test for some unknown exception that may occur and the below one is to assert some exception with custom message.

 @Rule
public ExpectedException exception=ExpectedException.none();

private Demo demo;
@Before
public void setup(){

    demo=new Demo();
}
@Test(expected=ArithmeticException.class)
public void testIfItThrowsAnyException() {

    demo.divideByZeroDemo(5, 0);

}

@Test
public void testExceptionWithMessage(){


    exception.expectMessage("Array is out of bound");
    exception.expect(ArrayIndexOutOfBoundsException.class);
    demo.exceptionWithMessage(new String[]{"This","is","a","demo"});
}

There are two ways of writing test case

  1. Annotate the test with the exception which is thrown by the method. Something like this @Test(expected = IndexOutOfBoundsException.class)
  2. You can simply catch the exception in the test class using the try catch block and assert on the message that is thrown from the method in test class.

    try{
    }
    catch(exception to be thrown from method e)
    {
         assertEquals("message", e.getmessage());
    }
    

I hope this answers your query Happy learning...


We can use an assertion fail after the method that must return an exception:

try{
   methodThatThrowMyException();
   Assert.fail("MyException is not thrown !");
} catch (final Exception exception) {
   // Verify if the thrown exception is instance of MyException, otherwise throws an assert failure
   assertTrue(exception instanceof MyException, "An exception other than MyException is thrown !");
   // In case of verifying the error message
   MyException myException = (MyException) exception;
   assertEquals("EXPECTED ERROR MESSAGE", myException.getMessage());
}




assert