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 io.codearte.catchexception.shade.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);
}
}