Lors de l'écriture de tests unitaires pour une API Java, il peut arriver que vous souhaitiez effectuer une validation plus détaillée d'une exception. C'est à dire. plus que ce qui est offert par l'annotation @test offerte par JUnit.
Par exemple, considérons une classe qui devrait intercepter une exception d'une autre interface, encapsule cette exception et lève l'exception encapsulée. Vous voudrez peut-être vérifier:
Le point principal ici est que vous souhaitez effectuer une validation supplémentaire d’une exception dans un test unitaire (et non un débat sur le fait de savoir si devrait vérifier des éléments comme le message d’exception).
Quelle est la bonne approche pour cela?
Dans JUnit 4, cela peut être facilement fait avec ExpectedException rule.
Voici un exemple de javadocs:
// These tests all pass.
public static class HasExpectedException {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void throwsNothing() {
// no exception expected, none thrown: passes.
}
@Test
public void throwsNullPointerException() {
thrown.expect(NullPointerException.class);
throw new NullPointerException();
}
@Test
public void throwsNullPointerExceptionWithMessage() {
thrown.expect(NullPointerException.class);
thrown.expectMessage("happened?");
thrown.expectMessage(startsWith("What"));
throw new NullPointerException("What happened?");
}
}
Comme indiqué dans votre réponse , c'est une bonne approche. De plus:
Vous pouvez envelopper la fonction expectException
dans une nouvelle annotation, appelée ExpectedException
.
Une méthode annotée ressemblerait à ceci:
@Test
@ExpectedException(class=WrapperException.class, message="Exception Message", causeException)
public void testAnExceptionWrappingFunction() {
//whatever you test
}
De cette façon, ce serait plus lisible, mais c'est exactement la même approche.
Une autre raison est: J'aime les annotations :)
En regardant les réponses proposées, vous pouvez vraiment ressentir la douleur de ne pas avoir de fermeture à Java. À mon humble avis, la solution la plus lisible est la bonne vieille tentative de capture.
@Test
public void test() {
...
...
try {
...
fail("No exception caught :(");
}
catch (RuntimeException ex) {
assertEquals(Whatever.class, ex.getCause().getClass());
assertEquals("Message", ex.getMessage());
}
}
Pour JUNIT 3.x
public void test(){
boolean thrown = false;
try{
mightThrowEx();
} catch ( Surprise expected ){
thrown = true;
assertEquals( "message", expected.getMessage());
}
assertTrue(thrown );
}
Jusqu'à ce poste, j'ai validé mon exception en faisant ceci:
try {
myObject.doThings();
fail("Should've thrown SomeException!");
} catch (SomeException e) {
assertEquals("something", e.getSomething());
}
J’ai passé quelques instants à réfléchir au problème et j’ai proposé ce qui suit (Java5, JUnit 3.x):
// Functor interface for exception assertion.
public interface AssertionContainer<T extends Throwable> {
void invoke() throws T;
void validate(T throwable);
Class<T> getType();
}
// Actual assertion method.
public <T extends Throwable> void assertThrowsException(AssertionContainer<T> functor) {
try {
functor.invoke();
fail("Should've thrown "+functor.getType()+"!");
} catch (Throwable exc) {
assertSame("Thrown exception was of the wrong type! Expected "+functor.getClass()+", actual "+exc.getType(),
exc.getClass(), functor.getType());
functor.validate((T) exc);
}
}
// Example implementation for servlet I used to actually test this. It was an inner class, actually.
AssertionContainer<ServletException> functor = new AssertionContainer<ServletException>() {
public void invoke() throws ServletException {
servlet.getRequiredParameter(request, "some_param");
}
public void validate(ServletException e) {
assertEquals("Parameter \"some_param\" wasn't found!", e.getMessage());
}
public Class<ServletException> getType() {
return ServletException.class;
}
}
// And this is how it's used.
assertThrowsException(functor);
En regardant ces deux-là, je ne peux pas décider lequel me plait le plus. J'imagine que c'est l'une de ces questions où atteindre un objectif (dans mon cas, la méthode d'assertion avec le paramètre foncteur) n'en vaut pas la peine à long terme, car il est beaucoup plus facile de faire ces 6+ de code pour affirmer l'essai bloc ..catch.
Encore une fois, peut-être que mon résultat de 10 minutes de résolution de problèmes de vendredi soir n’est tout simplement pas la façon la plus intelligente de le faire.
@akuhn:
Même sans fermeture, nous pouvons obtenir une solution plus lisible (en utilisant catch-exception ):
import static com.googlecode.catchexception.CatchException.*;
public void test() {
...
...
catchException(nastyBoy).doNastyStuff();
assertTrue(caughtException() instanceof WhateverException);
assertEquals("Message", caughtException().getMessage());
}
j'ai fait quelque chose de très simple
testBla(){
try {
someFailingMethod()
fail(); //method provided by junit
} catch(Exception e) {
//do nothing
}
}
La méthode d'assistance suivante (adaptée de this blog post) fait l'affaire:
/**
* Run a test body expecting an exception of the
* given class and with the given message.
*
* @param test To be executed and is expected to throw the exception.
* @param expectedException The type of the expected exception.
* @param expectedMessage If not null, should be the message of the expected exception.
* @param expectedCause If not null, should be the same as the cause of the received exception.
*/
public static void expectException(
Runnable test,
Class<? extends Throwable> expectedException,
String expectedMessage,
Throwable expectedCause) {
try {
test.run();
}
catch (Exception ex) {
assertSame(expectedException, ex.getClass());
if (expectedMessage != null) {
assertEquals(expectedMessage, ex.getMessage());
}
if (expectedCause != null) {
assertSame(expectedCause, ex.getCause());
}
return;
}
fail("Didn't find expected exception of type " + expectedException.getName());
}
Le code de test peut alors invoquer ceci comme suit:
TestHelper.expectException(
new Runnable() {
public void run() {
classInstanceBeingTested.methodThatThrows();
}
},
WrapperException.class,
"Exception Message",
causeException
);
Pour JUnit 5 c'est beaucoup plus facile:
@Test
void testAppleIsSweetAndRed() throws Exception {
IllegalArgumentException ex = assertThrows(
IllegalArgumentException.class,
() -> testClass.appleIsSweetAndRed("orange", "red", "sweet"));
assertEquals("this is the exception message", ex.getMessage());
assertEquals(NullPointerException.class, ex.getCause().getClass());
}
En renvoyant l'objet exception lui-même, assertThrows()
vous permet de tester chaque aspect des exceptions levées.
J'ai fait une aide semblable aux autres postés:
public class ExpectExceptionsExecutor {
private ExpectExceptionsExecutor() {
}
public static void execute(ExpectExceptionsTemplate e) {
Class<? extends Throwable> aClass = e.getExpectedException();
try {
Method method = ExpectExceptionsTemplate.class.getMethod("doInttemplate");
method.invoke(e);
} catch (NoSuchMethodException e1) {
throw new RuntimeException();
} catch (InvocationTargetException e1) {
Throwable throwable = e1.getTargetException();
if (!aClass.isAssignableFrom(throwable.getClass())) {
// assert false
fail("Exception isn't the one expected");
} else {
assertTrue("Exception captured ", true);
return;
}
;
} catch (IllegalAccessException e1) {
throw new RuntimeException();
}
fail("No exception has been thrown");
}
}
Et le modèle que le client doit implémenter
public interface ExpectExceptionsTemplate<T extends Throwable> {
/**
* Specify the type of exception that doInttemplate is expected to throw
* @return
*/
Class<T> getExpectedException();
/**
* Execute risky code inside this method
* TODO specify expected exception using an annotation
*/
public void doInttemplate();
}
Et le code client ressemblerait à ceci:
@Test
public void myTest() throws Exception {
ExpectExceptionsExecutor.execute(new ExpectExceptionsTemplate() {
@Override
public Class getExpectedException() {
return IllegalArgumentException.class;
}
@Override
public void doInttemplate() {
riskyMethod.doSomething(null);
}
});
}
Cela semble vraiment détaillé, mais si vous utilisez un IDE avec une bonne complétion automatique, il vous suffira d'écrire le type d'exception et le code à tester. (le reste sera fait par le IDE: D)