web-dev-qa-db-fra.com

Comment affirmer mon message d'exception avec l'annotation JUnit Test?

J'ai écrit quelques tests JUnit avec l'annotation @Test. Si ma méthode de test génère une exception vérifiée et si je souhaite associer le message à l'exception, existe-t-il un moyen de le faire avec une annotation JUnit @Test? Autant que je sache, JUnit 4.7 ne fournit pas cette fonctionnalité, mais est-ce que les versions futures l’offrent? Je sais que dans .NET, vous pouvez affirmer le message et la classe d'exception. Vous recherchez une fonctionnalité similaire dans le monde Java.

C'est ce que je veux:

@Test (expected = RuntimeException.class, message = "Employee ID is null")
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() {}
242
Cshah

Vous pouvez utiliser le @Rule annotation avec ExpectedException , comme ceci:

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

@Test
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() throws Exception {
    expectedEx.expect(RuntimeException.class);
    expectedEx.expectMessage("Employee ID is null");
    // do something that should throw the exception...
}

Notez que l'exemple dans la documentation ExpectedException est (actuellement) incorrect - il n'y a pas de constructeur public, vous devez donc utiliser ExpectedException.none().

441
Jesse Merriman

J'aime le @Rule answer. Cependant, si pour une raison quelconque vous ne voulez pas utiliser de règles. Il y a une troisième option.

@Test (expected = RuntimeException.class)
public void myTestMethod()
{
   try
   {
      //Run exception throwing operation here
   }
   catch(RuntimeException re)
   {
      String message = "Employee ID is null";
      assertEquals(message, re.getMessage());
      throw re;
    }
    fail("Employee Id Null exception did not throw!");
  }
32
Raystorm

Devez-vous utiliser @Test(expected=SomeException.class)? Lorsque nous devons affirmer le message réel de l'exception, c'est ce que nous faisons.

@Test
public void myTestMethod()
{
  try
  {
    final Integer employeeId = null;
    new Employee(employeeId);
    fail("Should have thrown SomeException but did not!");
  }
  catch( final SomeException e )
  {
    final String msg = "Employee ID is null";
    assertEquals(msg, e.getMessage());
  }
}
29
c_maker

En fait, la meilleure utilisation est avec try/catch. Pourquoi? Parce que vous pouvez contrôler l'endroit où vous attendez l'exception.

Considérons cet exemple:

@Test (expected = RuntimeException.class)
public void someTest() {
   // test preparation
   // actual test
}

Que se passe-t-il si un jour le code est modifié et que la préparation du test lève une exception RuntimeException? Dans ce cas, le test réel n'est même pas testé et même s'il ne lève aucune exception, le test réussira.

C'est pourquoi il est préférable d'utiliser try/catch plutôt que de compter sur l'annotation.

9
Krzysztof Cislo

Raystorm avait une bonne réponse. Je ne suis pas un grand fan de règles non plus. Je fais quelque chose de similaire, sauf que je crée la classe d’utilité suivante pour améliorer la lisibilité et la convivialité, ce qui est l’un des gros avantages des annotations. 

Ajoutez cette classe d'utilitaire:

import org.junit.Assert;

public abstract class ExpectedRuntimeExceptionAsserter {

    private String expectedExceptionMessage;

    public ExpectedRuntimeExceptionAsserter(String expectedExceptionMessage) {
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run(){
        try{
            expectException();
            Assert.fail(String.format("Expected a RuntimeException '%s'", expectedExceptionMessage));
        } catch (RuntimeException e){
            Assert.assertEquals("RuntimeException caught, but unexpected message", expectedExceptionMessage, e.getMessage());
        }
    }

    protected abstract void expectException();

}

Ensuite, pour mon test unitaire, tout ce dont j'ai besoin est ce code:

@Test
public void verifyAnonymousUserCantAccessPrivilegedResourceTest(){
    new ExpectedRuntimeExceptionAsserter("anonymous user can't access privileged resource"){
        @Override
        protected void expectException() {
            throw new RuntimeException("anonymous user can't access privileged resource");
        }
    }.run(); //passes test; expected exception is caught, and this @Test returns normally as "Passed"
}
7
user64141

Dans JUnit 4.13 (une fois publié), vous pouvez effectuer les opérations suivantes:

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;

...

@Test
void exceptionTesting() {
  IllegalArgumentException exception = assertThrows(
    IllegalArgumentException.class, 
    () -> { throw new IllegalArgumentException("a message"); }
  );

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

Cela fonctionne également dans JUnit 5 mais avec des importations différentes:

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

...
6
Johan Boberg

Si vous utilisez @Rule, l'ensemble d'exceptions est appliqué à toutes les méthodes de test de la classe Test. 

2
Jyothi

J'aime la réponse de user64141 mais j'ai constaté qu'elle pourrait être plus généralisée. Voici ma prise:

public abstract class ExpectedThrowableAsserter implements Runnable {

    private final Class<? extends Throwable> throwableClass;
    private final String expectedExceptionMessage;

    protected ExpectedThrowableAsserter(Class<? extends Throwable> throwableClass, String expectedExceptionMessage) {
        this.throwableClass = throwableClass;
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run() {
        try {
            expectException();
        } catch (Throwable e) {
            assertTrue(String.format("Caught unexpected %s", e.getClass().getSimpleName()), throwableClass.isInstance(e));
            assertEquals(String.format("%s caught, but unexpected message", throwableClass.getSimpleName()), expectedExceptionMessage, e.getMessage());
            return;
        }
        fail(String.format("Expected %s, but no exception was thrown.", throwableClass.getSimpleName()));
    }

    protected abstract void expectException();

}

Notez que laisser l'instruction "fail" dans le bloc try provoque la capture de l'exception d'assertion associée; utiliser return dans l'instruction catch empêche cela.

1
Addison Crump
@Test (expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "This is not allowed")
public void testInvalidValidation() throws Exception{
     //test code
}
0
aasha

Importez la bibliothèque catch-exception et utilisez-la. C'est beaucoup plus propre que la règle ExpectedException ou un try-catch.

Exemple de leur documentation:

import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;

// given: an empty list
List myList = new ArrayList();

// when: we try to get the first element of the list
catchException(myList).get(1);

// then: we expect an IndexOutOfBoundsException with message "Index: 1, Size: 0"
assertThat(caughtException(),
  allOf(
    instanceOf(IndexOutOfBoundsException.class),
    hasMessage("Index: 1, Size: 0"),
    hasNoCause()
  )
);
0
whistling_marmot