All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.catchexception.CatchException Maven / Gradle / Ivy

/**
 * Copyright (C) 2011 [email protected]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.catchexception;

import org.mockito.cglib.proxy.MethodInterceptor;

import com.googlecode.catchexception.apis.CatchExceptionBdd;
import com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers;
import com.googlecode.catchexception.internal.DelegatingInterceptor;
import com.googlecode.catchexception.internal.ExceptionHolder;
import com.googlecode.catchexception.internal.ExceptionProcessingInterceptor;
import com.googlecode.catchexception.internal.InterfaceOnlyProxyFactory;
import com.googlecode.catchexception.internal.SubclassProxyFactory;

/**
 * The methods of this class catch and verify exceptions in a single line of
 * code and make them available for further analysis.
 * 

* This Javadoc content is also available on the catch-exception web * page. * *

Documentation

*

* 1. How to use catch-exception?
* 2. What is this stuff actually good for?
* 3. How does it work internally?
* 4. When is the caught exception reset?
* 5. My code throws a ClassCastException. Why?
* 6. The exception is not caught. Why?
* 7. Do I have to care about memory leaks?
* 8. The caught exception is not available in another thread. * Why?
* 9. How do I catch an exception thrown by a static method?
* 11. Can I catch errors instead of exceptions?
* * * *
*

*

1. How to use catch-exception?

*

* The most basic usage is: *

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

// call customerService.prepareBilling(Prize.Zero)
// and catch the exception if any is thrown 
catchException(customerService).prepareBilling(Prize.Zero);

// assert that an IllegalArgumentException was thrown
assert caughtException() instanceof IllegalArgumentException;
*

* You can combine the two lines of code in a single one if you like: *

// call customerService.prepareBilling(Prize.Zero)
// and throw an ExceptionNotThrownAssertionError if
// the expected exception is not thrown 
verifyException(customerService, IllegalArgumentException.class).prepareBilling(Prize.Zero);
* There is a minor difference between both variants. In the first variant you * must start the JVM with option -ea to enable the assertion. The * second variant does not use JDK assertions and ,therefore, always verifies * the caught exception. *

* A third variant allows you to select the type of exceptions you want to catch * (no verification involved): *

// catch IllegalArgumentExceptions but no other exceptions 
catchException(customerService, IllegalArgumentException.class).prepareBilling(Prize.Zero);
*

* The fourth and last variant verifies that some exception is thrown, i.e. the * type of the exception does not matter: *

verifyException(customerService).prepareBilling(Prize.Zero);
*

* In all variants you can use caughtException() afterwards to * inspect the caught exception. *

* Finally, there some alternative ways to catch and verify exceptions: *

    *
  • {@link CatchExceptionBdd} - a BDD-like approach, *
  • {@link CatchExceptionHamcrestMatchers} - Hamcrest assertions *
*

2. What is this stuff actually good for?

*

* This class targets concise and robust code in tests. Dadid Saff, a commiter * to JUnit, has discussed this approach in 2007. Let me summarize the arguments here. *

* There are two advantages of the approach proposed here in comparison to the * use of try/catch blocks. *

    *
  • The test is more concise and easier to read. *
  • The test cannot be corrupted by a missing assertion. Assume you forgot to * type fail() behind the method call that is expected to throw an * exception. *
*

* There are also some advantages of this approach in comparison to test * runner-specific mechanisms that catch and verify exceptions. *

    *
  • A single test can verify more than one thrown exception. *
  • The test can verify the properties of the thrown exception after the * exception is caught. *
  • The test can specify by which method call the exception must be thrown. *
  • The test does not depend on a specific test runner (JUnit4, TestNG). *
*

*

3. How does it work internally?

*

* The method catchException(obj) wraps the given object with a * proxy that catches the exception, then (optionally) verifies the exception, * and finally attaches the exception to the current thread for further analysis. The known limitations for proxies apply. *

* Is both memory consumption and runtime a concern for you? Then use try/catch * blocks instead of this class. Because in this case the creation of proxies is * an unnecessary overhead. If only either memory consumption or runtime is an * issue for you, feel free to configure the cache of the underlying proxy * factories as appropriate. *

4. When is the caught exception reset?

*

* The Method {@link #caughtException()} returns the exception thrown by the * last method call on a proxied object in the current thread, i.e. it is reset * by calling a method on the proxied object. If the called method has not * thrown an exception, caughtException() returns null. *

* To reset the caught exception manually, call {@link #resetCaughtException()}. * At the moment there is no way to reset exceptions that have been caught in * other threads. *

5. My code throws a ClassCastException. Why?

*

* Example: *

StringBuilder sb = new StringBuilder();
catchException(sb).charAt(-2); // throws ClassCastException
*

* Probably you have tested a final class. Proxy factories usually try to * subclass the type of the proxied object. This is not possible if the original * class is final. But there is a way out. If the tested method belongs to an * interface, then you can cast the argument (here: sb) to that * interface or ,easier, change the declared type of the argument to the * interface type. This works because the created proxy is not longer required * to have the same type as the original class but it must only have the same * interface. *

// first variant
StringBuilder sb = new StringBuilder();
catchException((CharSequence) sb).charAt(-2); // works fine

// second variant
CharSequence sb = new StringBuilder();
catchException(sb).charAt(-2); // works fine
If the tested * method does no belong to an interface fall back to the try/catch-blocks or * use
Powermock * . *
// example for
 PowerMock with JUnit4
@RunWith(PowerMockRunner.class)
@PrepareForTest({ MyFinalType.class })
public class MyTest {
*

6. The exception is not caught. Why?

*

* Example: *

ServiceImpl impl = new ServiceImpl();
catchException(impl).do(); // do() is a final method that throws an exception
*

* Probably you have tested a final method. If that tested method belongs to an * interface you could use {@link #interfaces(Object)} to fix that problem. But * then the syntax starts to become ugly. *

Service api = new ServiceImpl();
catchException(interfaces(api)).do(); // works fine
I recommend * to use try/catch blocks in such cases. * *

7. Do I have to care about memory leaks?

*

* This library uses a {@link ThreadLocal}. ThreadLocals are known to cause * memory leaks if they refer to a class the garbage collector would like to * collect. If you use this library only for testing, then memory leaks do not * worry you. If you use this library for other purposes than testing, you * should care. *

*

8. The caught exception is not available in another thread. Why?

*

* The caught exception is saved at the thread the * exception is thrown in. This is the reason the exception is not visible * within any other thread. *

9. How do I catch an exception thrown by a static method?

*

* Unfortunately, catch-exception does not support this. Fall back on try/catch * blocks. *

10. Is there a way to get rid of the throws clause in my test * method?

*

* Example: *

public void testSomething() throws Exception {
    ...
    catchException(obj).do(); // do() throws a checked exception
No, * although the exception is always caught you cannot omit the throws clause in * your test method. *

11. Can I catch errors instead of exceptions?

*

* Yes, have a look at * {@link com.googlecode.catchexception.throwable.CatchThrowable} (in module * catch-throwable). * * @author rwoo * @since 16.09.2011 */ public class CatchException { /** * Returns the exception caught during the last call on the proxied object * in the current thread. * * @param * This type parameter makes some type casts redundant. * @return Returns the exception caught during the last call on the proxied * object in the current thread - if the call was made through a * proxy that has been created via * {@link #verifyException(Object, Class) verifyException()} or * {@link #catchException(Object, Class) catchException()}. Returns * null the proxy has not caught an exception. Returns null if the * caught exception belongs to a class that is no longer * {@link ClassLoader loaded}. */ public static E caughtException() { return ExceptionHolder. get(); } /** * Use it to verify that an exception is thrown and to get access to the * thrown exception (for further verifications). *

* The following example verifies that obj.doX() throws a Exception: *

verifyException(obj).doX(); // catch and verify
assert "foobar".equals(caughtException().getMessage()); // further analysis
*

* If doX() does not throw a Exception, then a * {@link ExceptionNotThrownAssertionError} is thrown. Otherwise the thrown * exception can be retrieved via {@link #caughtException()}. *

* * @param * The type of the given obj. * * @param obj * The instance that shall be proxied. Must not be * null. * @return Returns an object that verifies that each invocation on the * underlying object throws an exception. */ public static T verifyException(T obj) { return verifyException(obj, Exception.class); } /** * Use it to verify that an exception of specific type is thrown and to get * access to the thrown exception (for further verifications). *

* The following example verifies that obj.doX() throws a MyException: *

verifyException(obj, MyException.class).doX(); // catch and verify
assert "foobar".equals(caughtException().getMessage()); // further analysis
*

* If doX() does not throw a MyException, then a * {@link ExceptionNotThrownAssertionError} is thrown. Otherwise the thrown * exception can be retrieved via {@link #caughtException()}. *

* * @param * The type of the given obj. * * @param * The type of the exception that shall be caught. * @param obj * The instance that shall be proxied. Must not be * null. * @param clazz * The type of the exception that shall be thrown by the * underlying object. Must not be null. * @return Returns an object that verifies that each invocation on the * underlying object throws an exception of the given type. */ public static T verifyException(T obj, Class clazz) { return processException(obj, clazz, true); } /** * Use it to catch an exception and to get access to the thrown exception * (for further verifications). *

* In the following example you catch exceptions that are thrown by * obj.doX(): *

catchException(obj).doX(); // catch
if (caughtException() != null) {
    assert "foobar".equals(caughtException().getMessage()); // further analysis
}
* If doX() throws a exception, then {@link #caughtException()} * will return the caught exception. If doX() does not throw a * exception, then {@link #caughtException()} will return null. *

* * @param * The type of the given obj. * * @param obj * The instance that shall be proxied. Must not be * null. * @return Returns a proxy for the given object. The proxy catches * exceptions of the given type when a method on the proxy is * called. */ public static T catchException(T obj) { return processException(obj, Exception.class, false); } /** * Use it to catch an exception of a specific type and to get access to the * thrown exception (for further verifications). *

* In the following example you catch exceptions of type MyException that * are thrown by obj.doX(): *

catchException(obj, MyException.class).doX(); // catch
if (caughtException() != null) {
    assert "foobar".equals(caughtException().getMessage()); // further analysis
}
* If doX() throws a MyException, then * {@link #caughtException()} will return the caught exception. If * doX() does not throw a MyException, then * {@link #caughtException()} will return null. If * doX() throws an exception of another type, i.e. not a * subclass but another class, then this exception is not thrown and * {@link #caughtException()} will return null. *

* * @param * The type of the given obj. * * @param * The type of the exception that shall be caught. * @param obj * The instance that shall be proxied. Must not be * null. * @param clazz * The type of the exception that shall be caught. Must not be * null. * @return Returns a proxy for the given object. The proxy catches * exceptions of the given type when a method on the proxy is * called. */ public static T catchException(T obj, Class clazz) { return processException(obj, clazz, false); } /** * Creates a proxy that processes exceptions thrown by the underlying * object. *

* Delegates to * {@link SubclassProxyFactory#createProxy(Class, MethodInterceptor)} which * itself might delegate to * {@link InterfaceOnlyProxyFactory#createProxy(Class, MethodInterceptor)}. */ @SuppressWarnings("javadoc") private static T processException(T obj, Class exceptionClazz, boolean assertException) { if (obj == null) { throw new IllegalArgumentException("obj must not be null"); } return new SubclassProxyFactory(). createProxy(obj.getClass(), new ExceptionProcessingInterceptor(obj, exceptionClazz, assertException)); } /** * Returns a proxy that implements all interfaces of the underlying object. * * @param * must be an interface the object implements * @param obj * the object that created proxy will delegate all calls to * @return Returns a proxy that implements all interfaces of the underlying * object and delegates all calls to that underlying object. */ public static T interfaces(T obj) { if (obj == null) { throw new IllegalArgumentException("obj must not be null"); } return new InterfaceOnlyProxyFactory(). createProxy(obj.getClass(), new DelegatingInterceptor(obj)); } /** * Sets the {@link #caughtException() caught exception} to null. This does * not affect exceptions saved at threads other than the current one. *

* Actually you probably never need to call this method because each method * call on a proxied object in the current thread resets the caught * exception. But if you want to improve test isolation or if you want to * 'clean up' after testing (to avoid memory leaks), call the method before * or after testing. */ public static void resetCaughtException() { ExceptionHolder.set(null); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy