diff --git a/assertthrows/README.txt b/assertthrows/README.txt index 1194ed8..df1f779 100644 --- a/assertthrows/README.txt +++ b/assertthrows/README.txt @@ -3,3 +3,6 @@ Welcome to JUnit Contrib AssertThrows ===================================== Testing for Expected Exceptions + +For details, see: +src/site/resources/index.html diff --git a/assertthrows/src/main/java/org/junit/contrib/assertthrows/AssertThrows.java b/assertthrows/src/main/java/org/junit/contrib/assertthrows/AssertThrows.java index d9c9008..cf7ed08 100644 --- a/assertthrows/src/main/java/org/junit/contrib/assertthrows/AssertThrows.java +++ b/assertthrows/src/main/java/org/junit/contrib/assertthrows/AssertThrows.java @@ -16,11 +16,7 @@ */ package org.junit.contrib.assertthrows; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import org.junit.contrib.assertthrows.proxy.ProxyFactory; -import org.junit.contrib.assertthrows.proxy.ReflectionUtils; import org.junit.contrib.assertthrows.verify.ExceptionVerifier; import org.junit.contrib.assertthrows.verify.ResultVerifier; @@ -32,46 +28,55 @@ public abstract class AssertThrows { private final ResultVerifier verifier; + private Throwable lastThrown; /** - * Create a new AssertThrows object, and call the test method to verify the - * expected exception is thrown. - * - * @param expectedExceptionClass the expected exception class + * Create a new AssertThrows object, and call the test method + * to verify an exception or error is thrown. + *

+ * For the test to pass, the test() method must throw any kind + * of Exception or Error ( + * AssertionError, StackOverflowError, and so on). */ - public AssertThrows(Class expectedExceptionClass) { - this(new ExceptionVerifier(expectedExceptionClass, null)); + public AssertThrows() { + this(new ExceptionVerifier()); } /** - * Create a new AssertThrows object, and call the test method to verify the - * expected exception is thrown. + * Create a new AssertThrows object, and call the test method + * to verify the expected exception is thrown. + *

+ * For the test to pass, the class of the thrown exception must match the + * expected exception, or it must be a subclass of the expected exception. * - * @param expectedException the expected exception + * @param expectedExceptionClass the expected exception class (must not be + * null) */ - public AssertThrows(Exception expectedException) { - this(expectedException == null ? - new ExceptionVerifier(null, null) : - new ExceptionVerifier( - expectedException.getClass(), - expectedException.getMessage())); + public AssertThrows(Class expectedExceptionClass) { + this(new ExceptionVerifier(expectedExceptionClass)); } /** - * Create a new AssertThrows object, and call the test method to verify an - * exception or error is thrown. That means for a successful result, the - * test() method must throw any kind of Exception or Error (AssertionError, - * StackOverflowError, and so on). + * Create a new AssertThrows object, and call the test method + * to verify the expected exception is thrown. + *

+ * For the test to pass, the exception class must match exactly, and the message + * must match exactly. If the message of the expected exception is null, then + * the message of the thrown exception must also be null. + * + * @param expectedException the expected exception (must not be null) */ - public AssertThrows() { - this(new ExceptionVerifier(null, null)); + public AssertThrows(Exception expectedException) { + this(new ExceptionVerifier(expectedException)); } /** - * Create a new AssertThrows object, and call the test method as many times - * as the result verifier requests. It is usually not required to use this - * constructor, except to extend this facility to do something new, such as - * repeat a test until it works. + * Create a new AssertThrows object, and call the test method + * as many times as the result verifier requests. + *

+ * This constructor is usually not required within unit tests, except to + * extend this facility to do something new, such as repeat a test until it + * works. */ protected AssertThrows(ResultVerifier verifier) { this.verifier = verifier; @@ -84,61 +89,69 @@ protected AssertThrows(ResultVerifier verifier) { */ protected void verify() { while (true) { + lastThrown = null; try { test(); // can't call verifier.verify here, because it can // throw an exception itself (which must not be caught) } catch (Throwable e) { - if (verifier.verify(null, e, null)) { - continue; - } - break; + lastThrown = e; } - if (verifier.verify(null, null, null)) { - continue; + if (!verifier.verify(null, lastThrown, null)) { + return; } - break; } } /** - * Verify that the next method call on the object throws an exception. + * Verify that the next method call on the returned object throws an exception. + *

+ * For the test to pass, the method must throw any kind + * of Exception or Error ( + * AssertionError, StackOverflowError, and so on). * * @param the class of the object - * @param obj the object to wrap + * @param obj the object to wrap (must not be null) * @return a proxy for the object */ public static T assertThrows(T obj) { - return createVerifyingProxy(new ExceptionVerifier(null, null), obj); + return ExceptionVerifier.createVerifyingProxy( + new ExceptionVerifier(), obj); } /** - * Verify that the next method call on the object throws the expected + * Verify that the next method call on the returned object throws the expected * exception. + *

+ * For the test to pass, the class of the thrown exception must match the + * expected exception, or it must be a subclass of the expected exception. * * @param the class of the object - * @param expectedExceptionClass the expected exception class to be thrown - * @param obj the object to wrap + * @param expectedExceptionClass the expected exception class (must not be null) + * @param obj the object to wrap (must not be null) * @return a proxy for the object */ public static T assertThrows(Class expectedExceptionClass, T obj) { - return createVerifyingProxy(new ExceptionVerifier(expectedExceptionClass, null), obj); + return ExceptionVerifier.createVerifyingProxy( + new ExceptionVerifier(expectedExceptionClass), obj); } /** * Verify that the next method call on the object throws the expected * exception. + *

+ * For the test to pass, the exception class must match exactly, and the message + * must match exactly. If the message of the expected exception is null, then + * the message of the thrown exception must also be null. * * @param the class of the object - * @param expectedException the expected exception - * @param obj the object to wrap + * @param expectedException the expected exception (must not be null) + * @param obj the object to wrap (must not be null) * @return a proxy for the object */ public static T assertThrows(Exception expectedException, T obj) { - return createVerifyingProxy(expectedException == null ? - new ExceptionVerifier(null, null) : - new ExceptionVerifier(expectedException.getClass(), expectedException.getMessage()), - obj); + return ExceptionVerifier.createVerifyingProxy( + new ExceptionVerifier(expectedException), obj); } /** @@ -153,48 +166,20 @@ public static void useClassProxy(Class c) { } /** - * Create a verifying proxy for the given object. It is usually not required - * to use this method in a test directly, except to extend this facility to - * do something new, such as repeat a test until it works. + * The test method that is called. * - * @param the class of the object - * @param verifier the result verifier to call after each method call - * @param obj the object to wrap - * @return a proxy for the object - * @throws IllegalArgumentException if it was not possible to create a proxy - * for the passed object + * @throws Exception the expected exception */ - protected static T createVerifyingProxy(final ResultVerifier verifier, final T obj) { - InvocationHandler handler = new InvocationHandler() { - private Exception called = new Exception("No method was called on " + obj); - public void finalize() { - if (called != null) { - called.printStackTrace(System.err); - } - } - public Object invoke(Object proxy, Method method, Object[] args) throws Exception { - try { - called = null; - Object ret; - do { - ret = method.invoke(obj, args); - } while (verifier.verify(ret, null, method, args)); - return ret; - } catch (InvocationTargetException e) { - verifier.verify(null, e.getTargetException(), method, args); - return ReflectionUtils.getDefaultValue(method.getReturnType()); - } - } - }; - ProxyFactory factory = ProxyFactory.getFactory(obj.getClass()); - return factory.createProxy(obj, handler); - } + public abstract void test() throws Exception; /** - * The test method that is called. + * Get the last exception or error (if any) that was thrown by the method + * test(). * - * @throws Exception the expected exception + * @return the last thrown exception or error, or null */ - public abstract void test() throws Exception; + public Throwable getLastThrown() { + return lastThrown; + } } diff --git a/assertthrows/src/main/java/org/junit/contrib/assertthrows/proxy/CglibProxyFactory.java b/assertthrows/src/main/java/org/junit/contrib/assertthrows/proxy/CglibProxyFactory.java index e94538e..bd7a498 100644 --- a/assertthrows/src/main/java/org/junit/contrib/assertthrows/proxy/CglibProxyFactory.java +++ b/assertthrows/src/main/java/org/junit/contrib/assertthrows/proxy/CglibProxyFactory.java @@ -147,7 +147,7 @@ public String getClassName(String prefix, } /** - * An tool to create new objects, if possible without calling any constructors. + * A tool to create new objects, if possible without calling any constructors. */ interface ObjectCreator { Object newInstance(Class c) throws Exception; diff --git a/assertthrows/src/main/java/org/junit/contrib/assertthrows/proxy/Compiler.java b/assertthrows/src/main/java/org/junit/contrib/assertthrows/proxy/Compiler.java index 49ad200..ff3f95a 100644 --- a/assertthrows/src/main/java/org/junit/contrib/assertthrows/proxy/Compiler.java +++ b/assertthrows/src/main/java/org/junit/contrib/assertthrows/proxy/Compiler.java @@ -78,14 +78,12 @@ public class Compiler { /** * Set the source code for the specified class. - * This will reset all compiled classes. * * @param className the class name * @param source the source code */ public void setSource(String className, String source) { sources.put(className, source); - compiled.clear(); } /** diff --git a/assertthrows/src/main/java/org/junit/contrib/assertthrows/verify/ExceptionVerifier.java b/assertthrows/src/main/java/org/junit/contrib/assertthrows/verify/ExceptionVerifier.java index 8b815d1..c9ed0e2 100644 --- a/assertthrows/src/main/java/org/junit/contrib/assertthrows/verify/ExceptionVerifier.java +++ b/assertthrows/src/main/java/org/junit/contrib/assertthrows/verify/ExceptionVerifier.java @@ -16,7 +16,11 @@ */ package org.junit.contrib.assertthrows.verify; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import org.junit.contrib.assertthrows.proxy.ProxyFactory; +import org.junit.contrib.assertthrows.proxy.ReflectionUtils; /** * A result verifier that checks against an expected exception class. @@ -26,21 +30,90 @@ public class ExceptionVerifier implements ResultVerifier { private final Class expectedExceptionClass; - private final String expectedMessage; + private final Exception expectedException; /** - * Create a new verifier that checks against the given class (or any of it's - * subclasses), or doesn't check the exception class at all (if the passed - * class is null). + * Create a new verifier that only checks if an exception or error was thrown. + */ + public ExceptionVerifier() { + this.expectedExceptionClass = null; + this.expectedException = null; + } + + /** + * Create a new verifier that checks against a given exception class, or any + * of it's subclasses. * - * @param expectedExceptionClass the expected exception base class, or null - * @param expectedMessage the expected message, or null + * @param expectedExceptionClass the expected exception base class (may not + * be null) */ - public ExceptionVerifier( - Class expectedExceptionClass, - String expectedMessage) { + public ExceptionVerifier(Class expectedExceptionClass) { + if (expectedExceptionClass == null) { + throw new NullPointerException("The passed exception class is null"); + } this.expectedExceptionClass = expectedExceptionClass; - this.expectedMessage = expectedMessage; + this.expectedException = null; + } + + /** + * Create a new verifier that checks against a given exception class for an + * exact match, plus additionally checks the message. + * + * @param expectedException the expected exception (may not be null) + */ + public ExceptionVerifier(Exception expectedException) { + if (expectedException == null) { + throw new NullPointerException("The passed exception is null"); + } + this.expectedExceptionClass = expectedException.getClass(); + if (expectedExceptionClass == null) { + // overcareful + throw new NullPointerException("The passed exception class is null"); + } + this.expectedException = expectedException; + } + + /** + * Create a verifying proxy for the given object. It is usually not required + * to use this method in a test directly, except to extend this facility to + * do something new, such as repeat a test until it works. + * + * @param the class of the object + * @param verifier the result verifier to call after each method call + * @param obj the object to wrap + * @return a proxy for the object + * @throws IllegalArgumentException if it was not possible to create a proxy + * for the passed object + */ + public static T createVerifyingProxy(final ResultVerifier verifier, final T obj) { + if (obj == null) { + throw new NullPointerException("The passed object is null"); + } + InvocationHandler handler = new InvocationHandler() { + private Exception called = new Exception("No method was called on " + obj); + public void finalize() { + if (called != null) { + called.printStackTrace(System.err); + } + } + public Object invoke(Object proxy, Method method, Object[] args) throws Exception { + called = null; + while (true) { + Object ret = null; + Throwable thrown = null; + try { + ret = method.invoke(obj, args); + } catch (InvocationTargetException e) { + thrown = e.getTargetException(); + } + if (!verifier.verify(ret, thrown, method, args)) { + return ReflectionUtils.getDefaultValue(method.getReturnType()); + } + } + } + }; + ProxyFactory factory = ProxyFactory.getFactory(obj.getClass()); + return factory.createProxy(obj, handler); } public boolean verify(Object returnValue, Throwable t, Method m, Object... args) { @@ -51,25 +124,35 @@ public boolean verify(Object returnValue, Throwable t, Method m, Object... args) return false; } else if (expectedExceptionClass.isAssignableFrom(t.getClass())) { // matching expected class - if (expectedMessage == null) { - // don't verify the message + if (expectedException == null) { + // don't verify the exception itself return false; } - if (expectedMessage.equals(t.getMessage())) { - return false; + // the exception class must match exactly + if (expectedException.getClass().equals(t.getClass())) { + String expectedMsg = expectedException.getMessage(); + String msg = t.getMessage(); + if (expectedMsg == null) { + if (msg == null) { + // both messages are null + return false; + } + } else if (expectedMsg.equals(msg)) { + // messages match + return false; + } + String message = "Expected exception message <" + expectedMsg + + ">, but got <" + msg + ">"; + throw buildAssertionError(message, t); } - throw new AssertionError( - "Expected message:\n" + expectedMessage + "\n" + - "but was: " + t.getMessage()); } } - String expected; + String type; if (expectedExceptionClass == null) { - expected = "Expected an exception to be thrown,\n"; + type = ""; } else { - expected = "Expected an exception of type\n" + - expectedExceptionClass.getSimpleName() + - " to be thrown,\n"; + type = " of type\n" + + expectedExceptionClass.getSimpleName(); } String but; if (m == null) { @@ -89,11 +172,18 @@ public boolean verify(Object returnValue, Throwable t, Method m, Object... args) t.getClass().getSimpleName() + " (see in the 'Caused by' for the exception that was thrown)"; } - AssertionError ae = new AssertionError(expected + but + result); - if (t != null) { - ae.initCause(t); + String message = + "Expected an exception" + type + " to be thrown,\n" + + but + result; + throw buildAssertionError(message, t); + } + + private AssertionError buildAssertionError(String message, Throwable got) { + AssertionError ae = new AssertionError(message); + if (got != null) { + ae.initCause(got); } - throw ae; + return ae; } /** diff --git a/assertthrows/src/site/resources/index.html b/assertthrows/src/site/resources/index.html index 65fc01a..dbef6f2 100644 --- a/assertthrows/src/site/resources/index.html +++ b/assertthrows/src/site/resources/index.html @@ -30,6 +30,12 @@


+

+ +This is an alpha release. The package names, API, and features may change in a future release. + +

+

Contents